[SSG] Generate RSS file. Fix extension handling. URL in EpisodeList.
Episode.audio now takes path without extension. Parse url as part of argument parsing.
This commit is contained in:
parent
0080597858
commit
11ede38d1b
107
nova.py
107
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("<item>")
|
||||
s.write("\n")
|
||||
# Title
|
||||
s.write(f"<title><![CDATA[{ep.title}]]></title>")
|
||||
s.write("\n")
|
||||
# Description
|
||||
s.write("<description><![CDATA["
|
||||
f"{ep.config['description']}]]></description>")
|
||||
s.write("\n")
|
||||
# Date
|
||||
datestring = ep.date.strftime(
|
||||
'%a, %d %b, %Y %H:%M:%Sz GMT'
|
||||
)
|
||||
s.write(f"<pubDate>{datestring}</pubDate>")
|
||||
s.write("\n")
|
||||
# iTunes: explicit, author, subtitle, keywords
|
||||
s.write(f"<itunes:explicit>{ep.config['explicit']}"
|
||||
"</itunes:explicit>")
|
||||
s.write("\n")
|
||||
s.write(
|
||||
f"<itunes:author><![CDATA[{ep.config['author']}]]>"
|
||||
"</itunes:author>"
|
||||
)
|
||||
s.write("\n")
|
||||
s.write(
|
||||
"<itunes:subtitle><![CDATA["
|
||||
f"{ep.config['subtitle']}]]></itunes:subtitle>"
|
||||
)
|
||||
s.write("\n")
|
||||
s.write(
|
||||
f"<itunes:keywords>{','.join(ep.config['tags'])}"
|
||||
"</itunes:keywords>"
|
||||
)
|
||||
s.write("\n")
|
||||
s.write(f"<itunes:duration>{seconds_to_str(len(ep))}"
|
||||
"</itunes:duration>")
|
||||
s.write("\n")
|
||||
# Content (show_notes)
|
||||
s.write(
|
||||
f"<content:encoded><![CDATA[{ep.show_notes}]]>"
|
||||
"</content:encoded>"
|
||||
)
|
||||
s.write("\n")
|
||||
# GUID
|
||||
s.write(
|
||||
f"<guid isPermaLink=\"true\">{self.url}{ep.slug}"
|
||||
".html</guid>"
|
||||
)
|
||||
s.write("\n")
|
||||
# Enclosure
|
||||
audio = f'{self.url}assets/music/{ep.slug}.{ext}'
|
||||
size = path.getsize(f"{ep.audio}.{ext}")
|
||||
s.write(
|
||||
f'<enclosure url="{audio}" type="audio/{ext}" '
|
||||
f'length="{size}" />'
|
||||
)
|
||||
s.write("\n")
|
||||
# Categories
|
||||
for tag in ep.config["tags"]:
|
||||
s.write(f"<category><![CDATA[{tag}]]></category>")
|
||||
s.write("\n")
|
||||
s.write("</item>")
|
||||
s.write("\n")
|
||||
s.write("</channel>")
|
||||
s.write("\n")
|
||||
s.write("</rss>")
|
||||
|
||||
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<key>\w+):\s*(?P<value>.*)")
|
||||
|
@ -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
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
jinja2
|
||||
markdown
|
||||
mutagen
|
||||
|
|
Loading…
Reference in New Issue