From 56379a104f30494d42bf87e3ee67cdb7089ddbeb Mon Sep 17 00:00:00 2001 From: Lucas Cimon <925560+Lucas-C@users.noreply.github.com> Date: Mon, 6 May 2024 16:59:04 +0200 Subject: [PATCH] Adding support for audio files --- AUTHORS | 2 +- src/sigal/audio.py | 41 +++++++++++++++++++++++++++++++++++++++ src/sigal/audio_file.png | Bin 0 -> 1799 bytes src/sigal/gallery.py | 32 ++++++++++++++++++++++++++++++ src/sigal/settings.py | 3 +++ src/sigal/utils.py | 11 ++++++++++- 6 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/sigal/audio.py create mode 100644 src/sigal/audio_file.png 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 0000000000000000000000000000000000000000..b30845e6fad9c064eafb7bc7f2176a60d45ad80b GIT binary patch literal 1799 zcmaJ?c~sJg7XD?Lg$qxQD^mI<@u(=Gq9HDm+e=7AQAn~yW$`&V=7O4Jxn-`YZl3i*0_}EWN86y+T_;LX=wg==lwD7obTT6+ z0sue@>*qsIa@F_PsrpyGAEPAIXwLvo0Jv4Eu^FkRtj#EXga80wS^z-iIRN;oY-P#- zAQ=e&iUJg1HyHie+Z5uPSf<^}{%zlFGDLZeJYm}|iV$ObOq~Bn1Eg-)D@=ual zHaNRC2YjR+bu|LG(4%_1?9%?wIE($LC{-)N9@0nNTAZs#EDy#=U41Xzd|SJm{`)wc zzV_|yyB{+~Ui8ezqoLY)ec$O@D#&a7{Fr|vgjf-5l-ALb?%Pz%nXv+~H{DTfWrwZ( z@)u^jwfJ&_0)1<&K|=91x3L>FYxlj^n)wdr?=V0`U-478PDW*WeT4yWQjQz-=QFk=xVW>V{lF zhove=loA^@>&`W`X1F59I#O6;5lF8M7wd8wS`alDDsM3>5zzNW;)LY{@rcUljQ@Y8oQMtqh;~%KeiV<61@s0mxc4e z*Wn&Q)m7uy=J3xXQ#*3Z(H(b~AKR6Dw2+exW44}n@f{DS9sto>dF##9ajcOHc z9zJ7Lbe*szUKiuIXvvYIyH=7*+ikk%8vVOy8!HIkR|xp83Q$U3uI4U~%Hn<2)U;o~ zl8=-{lqPai<|qrfN$tfOLQtAtD#-f=I&ol#@aXR#ovqum3O)_>%nlAE_@?c|U)g!C zT8J2XTpdX~?xVK$*%I$q&il9!T82J(s!E|lDP1I=)s$m(TNLU)zgLhCJ;QA`P4LE_ z>ZESFeXF*=c1q{D2YGbFfP;pcPD^)u${P$UeD+xNS(`J7#Mv#le9)QqKPl&N+-vcfbSs;hvUd?MJnKL8`sv&oI`=PuE0;{^2A;E~PA3j|}rW3ymK_Xlw>mhdmea6;4|65u8 zDL2N6RB=Zvm!XbkwkOI$a1#8Y@w{fcnccy~38F8` zfr#$2@sr??fsu0NF`Z#M__z?nuK!)=NbV1t-xU7v+sn5OE=`N{wk-8u+9icu0(sX< zR|~s^UCzNx2e2Kt7xq3J>J^AAmbAGcd_2yA0Zld91?jV>VP4?Gz6CG;bNb+vuHuD#%j_kWuCfpr2}@ono&^N}PJ z??49De>?PbzZpJ7g9(dw(z4-Iff`(=fvNQiyVIYzsC;RD$qFz;a;#nqnv08H*qZXn zH8y*&ajH6t|2@Xq>W{i@SH~_n^}=$L-vY+)4P#Iv88i$fk)|ZT0qKCUMLO9cokQ)N tF!uHslmi@z#2}HGT-n0^2!1|EjiIOgy5;u)cvltzHq?e*%LmG)Mpd literal 0 HcmV?d00001 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):