diff --git a/Backend/Embeds.py b/Backend/Embeds.py index 0f8932c..63ee749 100644 --- a/Backend/Embeds.py +++ b/Backend/Embeds.py @@ -19,6 +19,7 @@ DescriptionExcerptLength = 200 DefaultDescriptions = { "directory": "A directory on SaraView.", "video": "A video on SaraView.", + "audio": "Audio on SaraView.", "image": "An image on SaraView.", "note": "A note on SaraView.", "file": "A file on SaraView.", @@ -89,6 +90,19 @@ def BuildOgTags(RootName: str, RelativePath: str, IsDirectoryUrl: bool, BaseUrl: Tags.append(_MetaTag("og:video:secure_url", MediaUrl)) Tags.append(_MetaTag("og:video:type", GetMimeType(EntryPath))) Tags.append(_MetaTag("og:description", DefaultDescriptions["video"])) + elif FileType == "audio": + # og:audio is the standard tag, but Discord doesn't play it. Discord + # does play og:video in an HTML5 player, which handles audio-only + # files too, so the audio is offered as both. + Tags.append(_MetaTag("og:type", "video.other")) + Tags.append(_MetaTag("og:audio", MediaUrl)) + Tags.append(_MetaTag("og:audio:secure_url", MediaUrl)) + Tags.append(_MetaTag("og:audio:type", GetMimeType(EntryPath))) + Tags.append(_MetaTag("og:video", MediaUrl)) + Tags.append(_MetaTag("og:video:url", MediaUrl)) + Tags.append(_MetaTag("og:video:secure_url", MediaUrl)) + Tags.append(_MetaTag("og:video:type", GetMimeType(EntryPath))) + Tags.append(_MetaTag("og:description", DefaultDescriptions["audio"])) elif FileType == "image": Tags.append(_MetaTag("og:type", "website")) Tags.append(_MetaTag("og:image", MediaUrl)) diff --git a/Backend/Explorer.py b/Backend/Explorer.py index 037e7a1..8e838b8 100644 --- a/Backend/Explorer.py +++ b/Backend/Explorer.py @@ -34,6 +34,10 @@ FileTypeMap = { ".webm": "video", ".mov": "video", ".mkv": "video", + ".flac": "audio", + ".ogg": "audio", + ".mp3": "audio", + ".wav": "audio", } diff --git a/Backend/Preview.py b/Backend/Preview.py index e749dfb..44c9f13 100644 --- a/Backend/Preview.py +++ b/Backend/Preview.py @@ -18,6 +18,10 @@ MimeTypeMap = { ".webm": "video/webm", ".mov": "video/quicktime", ".mkv": "video/x-matroska", + ".flac": "audio/flac", + ".ogg": "audio/ogg", + ".mp3": "audio/mpeg", + ".wav": "audio/wav", } diff --git a/Client/Browser.py b/Client/Browser.py index 0214c5b..5ebe58a 100644 --- a/Client/Browser.py +++ b/Client/Browser.py @@ -18,7 +18,8 @@ from Viewer import FileView DoubleClickWindow = 0.4 # seconds -TypeLabels = {"markdown": "Markdown", "text": "Text", "config": "Config", "html": "HTML", "image": "Image", "video": "Video"} +TypeLabels = {"markdown": "Markdown", "text": "Text", "config": "Config", "html": "HTML", "image": "Image", "video": "Video", + "audio": "Audio"} class Browser(ft.Column): diff --git a/Client/Core.py b/Client/Core.py index edb9d15..db068c0 100644 --- a/Client/Core.py +++ b/Client/Core.py @@ -85,6 +85,7 @@ FileIcons = { "html": ft.Icons.CODE, "image": ft.Icons.IMAGE_OUTLINED, "video": ft.Icons.MOVIE_OUTLINED, + "audio": ft.Icons.MUSIC_NOTE_OUTLINED, } _ThemeRoleMap = { diff --git a/Client/Sources.py b/Client/Sources.py index 97e7c68..f3cafd1 100644 --- a/Client/Sources.py +++ b/Client/Sources.py @@ -27,6 +27,7 @@ FileTypeMap = { ".html": "html", ".htm": "html", ".png": "image", ".jpg": "image", ".jpeg": "image", ".gif": "image", ".webp": "image", ".svg": "image", ".mp4": "video", ".webm": "video", ".mov": "video", ".mkv": "video", + ".flac": "audio", ".ogg": "audio", ".mp3": "audio", ".wav": "audio", } TextFileTypes = {"markdown", "text", "config", "html"} diff --git a/Client/Viewer.py b/Client/Viewer.py index 6f1324c..372b9a1 100644 --- a/Client/Viewer.py +++ b/Client/Viewer.py @@ -193,9 +193,10 @@ class FileView(ft.Container): return ft.Column(Views or [View], scroll=ft.ScrollMode.AUTO, expand=True) def _InfoView(self) -> ft.Control: - Icon = ft.Icons.MOVIE_OUTLINED if self.FileType == "video" else ft.Icons.INSERT_DRIVE_FILE_OUTLINED - Note = ("Videos play in your system's video player: use Open Externally." - if self.FileType == "video" else "SaraView can't show this type of file. Use Open Externally.") + Icon, Note = { + "video": (ft.Icons.MOVIE_OUTLINED, "Videos play in your system's video player: use Open Externally."), + "audio": (ft.Icons.MUSIC_NOTE_OUTLINED, "Audio plays in your system's music player: use Open Externally."), + }.get(self.FileType, (ft.Icons.INSERT_DRIVE_FILE_OUTLINED, "SaraView can't show this type of file. Use Open Externally.")) return ft.Column( [ ft.Icon(Icon, size=64, color=Palette.Mauve), diff --git a/Frontend/src/Components/PreviewPane.jsx b/Frontend/src/Components/PreviewPane.jsx index 828a2e8..fa21346 100644 --- a/Frontend/src/Components/PreviewPane.jsx +++ b/Frontend/src/Components/PreviewPane.jsx @@ -88,6 +88,10 @@ const LinkedFileTypeMap = { ".webm": "video", ".mov": "video", ".mkv": "video", + ".flac": "audio", + ".ogg": "audio", + ".mp3": "audio", + ".wav": "audio", }; function ClassifyLinkedFile(LinkedPath) { @@ -370,6 +374,22 @@ export default function PreviewPane({ ); } + if (FileType === "audio") { + return ( +
+
+ + Download + +
+
+
{FilePath.split("/").pop()}
+
+
+ ); + } + if (FileType === "html" && !IsEditing) { return (
diff --git a/Frontend/src/Styles/Theme.css b/Frontend/src/Styles/Theme.css index 38765e3..57b7bc9 100644 --- a/Frontend/src/Styles/Theme.css +++ b/Frontend/src/Styles/Theme.css @@ -968,6 +968,25 @@ img, max-height: 100%; } +.PreviewAudio { + margin: auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + padding: 24px; +} + +.PreviewAudioName { + color: var(--Purple); +} + +.PreviewAudio audio { + width: min(480px, 90vw); + accent-color: var(--Purple); + color-scheme: dark; /* the browser's dark controls, not a white bar */ +} + .PreviewFrame iframe { flex: 1; border: none; diff --git a/README.md b/README.md index 9acf9dd..57d7e38 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # SaraView A self-hosted web file previewer/editor. Obsidian's look, without the parts -you don't use, plus image and video previewing. Read-only integration with +you don't use, plus image, video and audio previewing. Read-only integration with SaraTag's per-file Book tags is included. It's made by Sarah, hence the name. :3 @@ -19,7 +19,8 @@ Example use-cases: - Browsing and editing markdown notes from your phone, without installing anything. -- Previewing images and videos that live next to your notes. +- Previewing images, videos and audio (FLAC, OGG, MP3, WAV) that live next to your notes. + Links to them embed in Discord and other apps that read Open Graph tags. - Giving a friend or family member their own login, with a directory only they can see. @@ -294,7 +295,7 @@ the web view's settings): **Live** shows it rendered and lets you edit in place, like Obsidian (click a heading to see and edit its `#`s; Esc puts it back), with Source (Ctrl+E) for the plain text; **Separate** keeps View and Edit as two modes. Images show in the app; -videos and other files open in their default program (Open Externally). +videos, audio and other files open in their default program (Open Externally). **Graph** (the toolbar's graph button or Ctrl+G here, the Graph button at the top of the web view's sidebar) shows everything under a root, or under