Compare commits
12 Commits
1474302d7e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4741080284 | |||
| 737147b457 | |||
| 123d35068f | |||
| 258aabe10f | |||
| bc070c4162 | |||
| cbf6bff779 | |||
| 080c731fd7 | |||
| 1b6f1ff1f2 | |||
| aa5998c3e3 | |||
| c65e949731 | |||
| ab51e5506e | |||
| 3701b37f4c |
17
README.md
17
README.md
@@ -1,14 +1,6 @@
|
|||||||
# auditui
|
# auditui
|
||||||
|
|
||||||
A terminal-based user interface (TUI) client for Audible, written in Python 3.
|
A terminal-based user interface (TUI) client for Audible, written in Python 3 : listen to your audiobooks (even offline), browse and manage your library, and more!
|
||||||
|
|
||||||
Listen to your audiobooks or podcasts, browse your library, and more.
|
|
||||||
|
|
||||||
## What it does and where are we
|
|
||||||
|
|
||||||
The project offers a TUI interface for browsing your Audible library, listing your books with progress information. You can sort by progress or title, show all books, or show only unfinished books which is the default.
|
|
||||||
|
|
||||||
You can also play a book by pressing `Enter` on a book in the list, and pause/resume the playback by pressing `Space`. `Left` and `Right` let you move 30 seconds backward/forward.
|
|
||||||
|
|
||||||
Currently, the only available theme is Catppuccin Mocha, following their [style guide](https://github.com/catppuccin/catppuccin/blob/main/docs/style-guide.md), as it's my preferred theme across most of my tools.
|
Currently, the only available theme is Catppuccin Mocha, following their [style guide](https://github.com/catppuccin/catppuccin/blob/main/docs/style-guide.md), as it's my preferred theme across most of my tools.
|
||||||
|
|
||||||
@@ -36,6 +28,7 @@ Please also note that as of now, you need to have [ffmpeg](https://ffmpeg.org/)
|
|||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
| ------------ | -------------------------- |
|
| ------------ | -------------------------- |
|
||||||
|
| `?` | Show help screen |
|
||||||
| `n` | Sort by name |
|
| `n` | Sort by name |
|
||||||
| `p` | Sort by progress |
|
| `p` | Sort by progress |
|
||||||
| `a` | Show all/unfinished |
|
| `a` | Show all/unfinished |
|
||||||
@@ -45,6 +38,7 @@ Please also note that as of now, you need to have [ffmpeg](https://ffmpeg.org/)
|
|||||||
| `right` | Seek forward 30 seconds |
|
| `right` | Seek forward 30 seconds |
|
||||||
| `ctrl+left` | Go to the previous chapter |
|
| `ctrl+left` | Go to the previous chapter |
|
||||||
| `ctrl+right` | Go to the next chapter |
|
| `ctrl+right` | Go to the next chapter |
|
||||||
|
| `d` | Download/delete from cache |
|
||||||
| `q` | Quit the application |
|
| `q` | Quit the application |
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
@@ -58,9 +52,10 @@ Please also note that as of now, you need to have [ffmpeg](https://ffmpeg.org/)
|
|||||||
- [x] add a control to jump 30s earlier/later
|
- [x] add a control to jump 30s earlier/later
|
||||||
- [x] add control to go to the previous/next chapter
|
- [x] add control to go to the previous/next chapter
|
||||||
- [x] save/resume playback of a book from the last position, regardless of which device was used previously
|
- [x] save/resume playback of a book from the last position, regardless of which device was used previously
|
||||||
- [ ] mark a book as finished or unfinished
|
- [x] download/remove a book in the cache without having to play it
|
||||||
- [ ] download/remove a book in the cache without having to play it
|
- [x] add a help screen with all the keybindings
|
||||||
- [ ] increase/decrease reading speed
|
- [ ] increase/decrease reading speed
|
||||||
|
- [ ] mark a book as finished or unfinished
|
||||||
- [ ] get your stats in a separated pane
|
- [ ] get your stats in a separated pane
|
||||||
- [ ] filter books on views
|
- [ ] filter books on views
|
||||||
- [ ] search in your book library
|
- [ ] search in your book library
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from .table_utils import (
|
|||||||
filter_unfinished_items,
|
filter_unfinished_items,
|
||||||
format_item_as_row,
|
format_item_as_row,
|
||||||
)
|
)
|
||||||
|
from .ui import HelpScreen
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from textual.widgets._data_table import ColumnKey
|
from textual.widgets._data_table import ColumnKey
|
||||||
@@ -29,8 +30,10 @@ class Auditui(App):
|
|||||||
"""Main application class for the Audible TUI app."""
|
"""Main application class for the Audible TUI app."""
|
||||||
|
|
||||||
theme = "textual-dark"
|
theme = "textual-dark"
|
||||||
|
SHOW_PALETTE = False
|
||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
|
("?", "show_help", "Help"),
|
||||||
("n", "sort", "Sort by name"),
|
("n", "sort", "Sort by name"),
|
||||||
("p", "sort_by_progress", "Sort by progress"),
|
("p", "sort_by_progress", "Sort by progress"),
|
||||||
("a", "show_all", "All/Unfinished"),
|
("a", "show_all", "All/Unfinished"),
|
||||||
@@ -75,7 +78,7 @@ class Auditui(App):
|
|||||||
yield table
|
yield table
|
||||||
yield Static("", id="progress_info")
|
yield Static("", id="progress_info")
|
||||||
yield ProgressBar(id="progress_bar", show_eta=False, show_percentage=False, total=100)
|
yield ProgressBar(id="progress_bar", show_eta=False, show_percentage=False, total=100)
|
||||||
yield Footer()
|
yield Footer(show_command_palette=False)
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""Initialize the table and start fetching library data."""
|
"""Initialize the table and start fetching library data."""
|
||||||
@@ -291,6 +294,10 @@ class Auditui(App):
|
|||||||
"""Show message when no playback is active."""
|
"""Show message when no playback is active."""
|
||||||
self.update_status("No playback active. Press Enter to play a book.")
|
self.update_status("No playback active. Press Enter to play a book.")
|
||||||
|
|
||||||
|
def action_show_help(self) -> None:
|
||||||
|
"""Show the help screen with all keybindings."""
|
||||||
|
self.push_screen(HelpScreen())
|
||||||
|
|
||||||
def _check_playback_status(self) -> None:
|
def _check_playback_status(self) -> None:
|
||||||
"""Check if playback process has finished and update state accordingly."""
|
"""Check if playback process has finished and update state accordingly."""
|
||||||
message = self.playback.check_status()
|
message = self.playback.check_status()
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ Header {
|
|||||||
Footer {
|
Footer {
|
||||||
background: #181825;
|
background: #181825;
|
||||||
color: #bac2de;
|
color: #bac2de;
|
||||||
height: 1;
|
height: 2;
|
||||||
padding: 0 1;
|
padding: 0 1;
|
||||||
scrollbar-size: 0 0;
|
scrollbar-size: 0 0;
|
||||||
overflow-x: hidden;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ FooterKey.-grouped,
|
|||||||
Footer.-compact FooterKey {
|
Footer.-compact FooterKey {
|
||||||
background: #181825;
|
background: #181825;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 2 0 0;
|
margin: 0 1 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
FooterKey .footer-key--key {
|
FooterKey .footer-key--key {
|
||||||
@@ -140,6 +140,97 @@ ProgressBar#progress_bar > .progress-bar--track {
|
|||||||
|
|
||||||
ProgressBar#progress_bar > .progress-bar--bar {
|
ProgressBar#progress_bar > .progress-bar--bar {
|
||||||
background: #a6e3a1;
|
background: #a6e3a1;
|
||||||
width: auto;
|
}
|
||||||
|
|
||||||
|
HelpScreen {
|
||||||
|
align: center middle;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_container {
|
||||||
|
width: 70;
|
||||||
|
height: auto;
|
||||||
|
max-height: 85%;
|
||||||
|
min-height: 20;
|
||||||
|
background: #1e1e2e;
|
||||||
|
border: thick #89b4fa;
|
||||||
|
padding: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_title {
|
||||||
|
text-align: center;
|
||||||
|
text-style: bold;
|
||||||
|
color: #89b4fa;
|
||||||
|
margin-bottom: 2;
|
||||||
|
padding-bottom: 1;
|
||||||
|
border-bottom: solid #585b70;
|
||||||
|
height: 3;
|
||||||
|
align: center middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_content {
|
||||||
|
width: 100%;
|
||||||
|
height: 1fr;
|
||||||
|
padding: 1 0;
|
||||||
|
margin: 1 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-size: 0 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_content > .scrollbar--vertical {
|
||||||
|
background: #313244;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_content > .scrollbar--vertical > .scrollbar--track {
|
||||||
|
background: #181825;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_content > .scrollbar--vertical > .scrollbar--handle {
|
||||||
|
background: #585b70;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_content > .scrollbar--vertical > .scrollbar--handle:hover {
|
||||||
|
background: #45475a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help_row {
|
||||||
|
height: 3;
|
||||||
|
margin: 0 0 1 0;
|
||||||
|
padding: 0 1;
|
||||||
|
background: #181825;
|
||||||
|
border: solid #313244;
|
||||||
|
align: left middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help_row:hover {
|
||||||
|
background: #313244;
|
||||||
|
border: solid #45475a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help_key {
|
||||||
|
width: 20;
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 2 0 0;
|
||||||
|
color: #f9e2af;
|
||||||
|
text-style: bold;
|
||||||
|
align: right middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help_action {
|
||||||
|
width: 1fr;
|
||||||
|
text-align: left;
|
||||||
|
padding: 0 0 0 2;
|
||||||
|
color: #cdd6f4;
|
||||||
|
align: left middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#help_footer {
|
||||||
|
text-align: center;
|
||||||
|
color: #bac2de;
|
||||||
|
margin-top: 2;
|
||||||
|
padding-top: 1;
|
||||||
|
border-top: solid #585b70;
|
||||||
|
height: 3;
|
||||||
|
align: center middle;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|||||||
33
auditui/ui.py
Normal file
33
auditui/ui.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
"""UI components for the Auditui application."""
|
||||||
|
|
||||||
|
from textual.app import ComposeResult
|
||||||
|
from textual.containers import Container, Horizontal, ScrollableContainer
|
||||||
|
from textual.screen import ModalScreen
|
||||||
|
from textual.widgets import Static
|
||||||
|
|
||||||
|
|
||||||
|
class HelpScreen(ModalScreen):
|
||||||
|
"""Help screen displaying all available keybindings."""
|
||||||
|
|
||||||
|
BINDINGS = [("escape", "dismiss", "Close"), ("?", "dismiss", "Close")]
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
with Container(id="help_container"):
|
||||||
|
yield Static("Key Bindings", id="help_title")
|
||||||
|
with ScrollableContainer(id="help_content"):
|
||||||
|
bindings = self.app.BINDINGS
|
||||||
|
for binding in bindings:
|
||||||
|
if isinstance(binding, tuple):
|
||||||
|
key, action, description = binding
|
||||||
|
else:
|
||||||
|
key = binding.key
|
||||||
|
description = binding.description
|
||||||
|
key_display = key.replace(
|
||||||
|
"ctrl+", "^").replace("left", "←").replace("right", "→").replace("space", "Space").replace("enter", "Enter")
|
||||||
|
with Horizontal(classes="help_row"):
|
||||||
|
yield Static(f"[bold #f9e2af]{key_display}[/]", classes="help_key")
|
||||||
|
yield Static(description, classes="help_action")
|
||||||
|
yield Static("Press [bold #f9e2af]?[/] or [bold #f9e2af]Escape[/] to close", id="help_footer")
|
||||||
|
|
||||||
|
def action_dismiss(self) -> None:
|
||||||
|
self.dismiss()
|
||||||
7
main.py
7
main.py
@@ -2,7 +2,6 @@
|
|||||||
"""Auditui entrypoint."""
|
"""Auditui entrypoint."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from auditui.app import Auditui
|
from auditui.app import Auditui
|
||||||
from auditui.auth import authenticate
|
from auditui.auth import authenticate
|
||||||
@@ -24,7 +23,7 @@ def main() -> None:
|
|||||||
config_dir = AUTH_PATH.parent
|
config_dir = AUTH_PATH.parent
|
||||||
|
|
||||||
if not config_dir.exists():
|
if not config_dir.exists():
|
||||||
print("No configuration yet, please run 'auditui configure' to create it")
|
print("No configuration yet, please run 'auditui configure'.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -32,9 +31,9 @@ def main() -> None:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"Authentication error: {exc}")
|
print(f"Authentication error: {exc}")
|
||||||
if not AUTH_PATH.exists():
|
if not AUTH_PATH.exists():
|
||||||
print("No configuration yet, please run 'auditui configure' to create it")
|
print("No configuration yet, please run 'auditui configure'.")
|
||||||
else:
|
else:
|
||||||
print("Please re-authenticate by running 'auditui configure'")
|
print("Please re-authenticate by running 'auditui configure'.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
app = Auditui(auth=auth, client=client)
|
app = Auditui(auth=auth, client=client)
|
||||||
|
|||||||
Reference in New Issue
Block a user