diff --git a/AUTHORS b/AUTHORS index 9be0cc4..49e818f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,7 +36,7 @@ alphabetical order): - Keith Feldman - Keith Johnson - Kevin Murray -- Lucas Cimon +- Lucas Cimon (@Lucas-C) - Lukas Vacek - Luke Cyca (@lukecyca) - Mate Lakat diff --git a/src/sigal/audio.py b/src/sigal/audio.py new file mode 100644 index 0000000..2b031c4 --- /dev/null +++ b/src/sigal/audio.py @@ -0,0 +1,41 @@ +# Copyright (c) 2013 - Christophe-Marie Duquesne +# Copyright (c) 2013-2023 - Simon Conseil +# Copyright (c) 2021 - Keith Feldman + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +from os.path import dirname, join + +from . import utils +from .utils import is_valid_html5_audio + +AUDIO_THUMB_FILE = join(dirname(__file__), "audio_file.png") + + +def process_audio(media): + """Process an audio file: create thumbnail.""" + settings = media.settings + + with utils.raise_if_debug() as status: + utils.copy(media.src_path, media.dst_path, symlink=settings["orig_link"]) + + if settings["make_thumbs"]: + utils.copy(AUDIO_THUMB_FILE, media.thumb_path) + + return status.value diff --git a/src/sigal/audio_file.png b/src/sigal/audio_file.png new file mode 100644 index 0000000..b30845e Binary files /dev/null and b/src/sigal/audio_file.png differ diff --git a/src/sigal/gallery.py b/src/sigal/gallery.py index 8adfabb..5f1004f 100644 --- a/src/sigal/gallery.py +++ b/src/sigal/gallery.py @@ -53,11 +53,13 @@ from .utils import ( copy, get_mime, get_mod_date, + is_valid_html5_audio, is_valid_html5_video, read_markdown, should_reprocess_album, url_from_path, ) +from .audio import process_audio from .video import process_video from .writer import AlbumListPageWriter, AlbumPageWriter @@ -345,6 +347,32 @@ class Video(Media): return self._get_file_date() +class Audio(Media): + """Gather all informations on an audio file.""" + + type = "audio" + + def __init__(self, filename, path, settings): + super().__init__(filename, path, settings) + self.mime = get_mime(self.src_ext) + + @cached_property + def date(self): + """The date from the Date metadata if available, or from the file date.""" + if "date" in self.meta: + try: + self.logger.debug( + "Reading date from image metadata : %s", self.src_filename + ) + return datetime.fromisoformat(self.meta["date"][0]) + except Exception: + self.logger.debug( + "Reading date from image metadata failed : %s", self.src_filename + ) + # If no date is found in the metadata, return the file date. + return self._get_file_date() + + class Album: """Gather all informations on an album. @@ -403,6 +431,8 @@ class Album: media = Image(f, self.path, settings) elif ext.lower() in settings["video_extensions"]: media = Video(f, self.path, settings) + elif ext.lower() in settings["audio_extensions"]: + media = Audio(f, self.path, settings) # Allow modification of the media, including overriding the class # type for the media. @@ -964,6 +994,8 @@ def process_file(media): processor = process_image elif media.type == "video": processor = process_video + elif media.type == "audio": + processor = process_audio # Allow overriding of the processor result = signals.process_file.send(media, processor=processor) diff --git a/src/sigal/settings.py b/src/sigal/settings.py index 60cee8b..752a77d 100644 --- a/src/sigal/settings.py +++ b/src/sigal/settings.py @@ -87,6 +87,7 @@ _DEFAULT_CONFIG = { "video_format": "webm", "video_always_convert": False, "video_size": (480, 360), + "audio_extensions": [".m4a", ".mp3", ".ogg", ".wav"], "watermark": "", "webm_options": ["-crf", "10", "-b:v", "1.6M", "-qmin", "4", "-qmax", "63"], "webm_options_second_pass": None, @@ -121,6 +122,8 @@ def get_thumb(settings, filename): if ext.lower() in settings["video_extensions"]: ext = ".jpg" + if ext.lower() in settings["audio_extensions"]: + ext = ".png" # extension of sigal.audio.AUDIO_THUMB_FILE return join( path, settings["thumb_dir"], diff --git a/src/sigal/utils.py b/src/sigal/utils.py index dc95f7b..89856c6 100644 --- a/src/sigal/utils.py +++ b/src/sigal/utils.py @@ -35,6 +35,7 @@ from sigal.settings import Status logger = logging.getLogger(__name__) MD = None VIDEO_MIMES = {".mp4": "video/mp4", ".webm": "video/webm", ".ogv": "video/ogg"} +AUDIO_MIMES = {".m4a": "audio/m4a", ".mp3": "audio/mpeg", ".ogg": "audio/ogg", ".wav": "audio/x-wav"} class Devnull: @@ -146,9 +147,17 @@ def is_valid_html5_video(ext): return ext in VIDEO_MIMES.keys() +def is_valid_html5_audio(ext): + """Checks if ext is a supported HTML5 video.""" + return ext in AUDIO_MIMES.keys() + + def get_mime(ext): """Returns mime type for extension.""" - return VIDEO_MIMES[ext] + mime_type = VIDEO_MIMES.get(ext) or AUDIO_MIMES.get(ext) + if not mime_type: + raise RuntimeError(f"Could not figure mime type, unknown file extension: {ext}") + return mime_type def init_plugins(settings):