From 11ede38d1bb6b5f3634f2bb759a94a33fa1e7439 Mon Sep 17 00:00:00 2001 From: Ceda EI Date: Wed, 5 Feb 2020 04:59:57 +0530 Subject: [PATCH] [SSG] Generate RSS file. Fix extension handling. URL in EpisodeList. Episode.audio now takes path without extension. Parse url as part of argument parsing. --- nova.py | 107 +++++++++++++++++++++++++++++++++++++++++++---- requirements.txt | 1 + 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/nova.py b/nova.py index 2530e2a..7ff7f01 100755 --- a/nova.py +++ b/nova.py @@ -14,6 +14,7 @@ import sys import jinja2 import markdown +from mutagen.mp3 import MP3 def gen_name(date, slug): @@ -21,10 +22,17 @@ def gen_name(date, slug): return date.strftime("%Y-%m-%d-") + slug +def seconds_to_str(seconds): + "Convert seconds to a string of hh:mm:ss" + seconds = int(seconds) + return f"{seconds // 3600:02}:{(seconds % 3600) // 60:02}:{seconds % 60:02}" + + class EpisodeList(UserList): "Represents list of episodes" - def __init__(self, data, output, template, archives): + def __init__(self, url, data, output, template, archives): super().__init__(data) + self.url = url self.output = output self.template = template self.archives = archives @@ -44,8 +52,80 @@ class EpisodeList(UserList): gen_name(episode.date, episode.slug) + ".jpg") episode.store_thumbnail(location) - def generate_atom(self): - "Generates the Atom feed" + def generate_rss(self, header): + "Generates the RSS Feed" + with open(self.output + "feed_mp3.rss", "w") as mp3, \ + open(self.output + "feed_ogg.rss", "w") as ogg: + # pylint: disable = invalid-name + for s, ext in ((mp3, "mp3"), (ogg, "ogg")): + s.write(header) + for ep in self.data: + s.write("") + s.write("\n") + # Title + s.write(f"<![CDATA[{ep.title}]]>") + s.write("\n") + # Description + s.write("") + s.write("\n") + # Date + datestring = ep.date.strftime( + '%a, %d %b, %Y %H:%M:%Sz GMT' + ) + s.write(f"{datestring}") + s.write("\n") + # iTunes: explicit, author, subtitle, keywords + s.write(f"{ep.config['explicit']}" + "") + s.write("\n") + s.write( + f"" + "" + ) + s.write("\n") + s.write( + "" + ) + s.write("\n") + s.write( + f"{','.join(ep.config['tags'])}" + "" + ) + s.write("\n") + s.write(f"{seconds_to_str(len(ep))}" + "") + s.write("\n") + # Content (show_notes) + s.write( + f"" + "" + ) + s.write("\n") + # GUID + s.write( + f"{self.url}{ep.slug}" + ".html" + ) + s.write("\n") + # Enclosure + audio = f'{self.url}assets/music/{ep.slug}.{ext}' + size = path.getsize(f"{ep.audio}.{ext}") + s.write( + f'' + ) + s.write("\n") + # Categories + for tag in ep.config["tags"]: + s.write(f"") + s.write("\n") + s.write("") + s.write("\n") + s.write("") + s.write("\n") + s.write("") def generate_archives(self): "Generates archives page" @@ -85,7 +165,8 @@ class EpisodeList(UserList): audio = (self.output + "assets/audio/" + gen_name(episode.date, episode.slug) + ".mp3") shutil.copy2(episode.video, video) - shutil.copy2(episode.audio, audio) + shutil.copy2(episode.audio + ".mp3", audio) + shutil.copy2(episode.audio + ".ogg", audio) with open(html, "w") as file: file.write(episode.render(self.template, thumbnail)) @@ -104,6 +185,7 @@ class Episode: self.video = video_src self.audio = audio_src self.config = config + self.length = MP3(audio_src + ".mp3").info.length def render(self, template, thumbnail_src): "Renders the Episode with the given template" @@ -120,23 +202,28 @@ class Episode: "1", location] subprocess.run(args, check=False) + def __len__(self): + return int(self.length) + def parse_args(): "Parses arguments" parser = argparse.ArgumentParser() parser.add_argument("input_dir", help="Input directory") parser.add_argument("output_dir", help="Output directory") + parser.add_argument("url", help="Base URL of website") args = parser.parse_args() input_dir = args.input_dir.rstrip("/") + "/" output_dir = args.output_dir.rstrip("/") + "/" - return input_dir, output_dir + url = args.url.rstrip("/") + "/" + return input_dir, output_dir, url class ParseError(ValueError): "Error raised while parsing a file" -def parse_file(file, array_keys=("categories")): +def parse_file(file, array_keys=("tags")): "Parses a file" config = {} kv_re = re.compile(r"(?P\w+):\s*(?P.*)") @@ -160,7 +247,7 @@ def parse_file(file, array_keys=("categories")): def main(args): "Main method" root = path.dirname(sys.argv[0]).rstrip("/") + "/" - input_dir, output_dir = args + input_dir, output_dir, url = args # Input validation paths = [ @@ -182,6 +269,7 @@ def main(args): ) podcast = EpisodeList( + url, [], output_dir, env.get_template("index.html"), @@ -210,7 +298,7 @@ def main(args): config["title"], show_notes, input_dir + "videos/" + gen_name(date, slug) + ".mp4", - input_dir + "audio/" + gen_name(date, slug) + ".mp3", + input_dir + "audio/" + gen_name(date, slug), config ) ) @@ -230,7 +318,8 @@ def main(args): podcast.sort() podcast.generate_thumbnails() podcast.generate_archives() - podcast.generate_atom() + with open(input_dir + "header.rss") as header: + podcast.generate_rss(header.read()) podcast.generate_site(root) shutil.copytree(input_dir + "overrides", output_dir, dirs_exist_ok=True) return 0 diff --git a/requirements.txt b/requirements.txt index e194905..934848d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ jinja2 markdown +mutagen