Compare commits
1 Commits
master
...
584870e699
| Author | SHA1 | Date | |
|---|---|---|---|
| 584870e699 |
@@ -1,7 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block left_content %}
|
|
||||||
<ul class="archives">
|
|
||||||
{% for episode in episodes %}
|
|
||||||
<li><a href="../{{ episode.slug }}">{{ episode.title }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
{% endblock %}
|
|
||||||
BIN
assets/fonts/Comfortaa-Latin-Light.woff
Normal file
BIN
assets/fonts/Comfortaa-Latin-Light.woff
Normal file
Binary file not shown.
BIN
assets/fonts/Comfortaa-Latin.woff2
Normal file
BIN
assets/fonts/Comfortaa-Latin.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
12
assets/fork-awesome/css/fork-awesome.min.css
vendored
12
assets/fork-awesome/css/fork-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 470 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 5.4 KiB |
85
base.html
85
base.html
@@ -1,85 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<title>{% block title %}{{ title }} | Redacted Life{% endblock %}</title>
|
|
||||||
<link href="{{ relative }}/assets/css/index.css" rel="stylesheet" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{{ relative }}/assets/plyr/plyr.css" />
|
|
||||||
|
|
||||||
<!-- Metadata -->
|
|
||||||
<meta name="description" content="An audiocast on Linux and libre software with a hard spin on personal privacy and security">
|
|
||||||
<meta http-equiv="X-Clacks-Overhead" content="GNU Terry Pratchett, Lloyd Barlowe" />
|
|
||||||
<link rel="canonical" href="https://redacted.life/">
|
|
||||||
<link rel="me" href="https://masto.nixnet.xyz/@amolith">
|
|
||||||
<link rel="me" href="https://masto.nixnet.xyz/@RedactedLife">
|
|
||||||
<link rel="me" href="https://social.nixnet.services/@RedactedLife">
|
|
||||||
{% block stylesheets %}{% endblock %}
|
|
||||||
|
|
||||||
<!-- Social: Twitter -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
<meta name="twitter:title" content="Redacted Life" />
|
|
||||||
<meta name="twitter:description" content="An audiocast on Linux and libre software with a hard spin on personal privacy and security" />
|
|
||||||
<meta name="twitter:image" content="/assets/media/cover-site.png" />
|
|
||||||
|
|
||||||
<!-- Social: OpenGraph -->
|
|
||||||
<meta property="og:locale" content="en_US">
|
|
||||||
<meta property="og:type" content="article">
|
|
||||||
<meta property="og:title" content="Redacted Life">
|
|
||||||
<meta property="og:description" content="An audiocast on Linux and libre software with a hard spin on personal privacy and security">
|
|
||||||
<meta property="og:url" content="https://redacted.life">
|
|
||||||
<meta property="og:site_name" content="Redacted Life">
|
|
||||||
<meta property="og:image" content="/assets/media/cover-site.png" />
|
|
||||||
|
|
||||||
<!-- Favicons -->
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ relative }}/assets/favicons/apple-touch-icon.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ relative }}/assets/favicons/favicon-32x32.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ relative }}/assets/favicons/favicon-16x16.png">
|
|
||||||
<link rel="manifest" href="{{ relative }}/assets/favicons/site.webmanifest">
|
|
||||||
<link rel="mask-icon" href="{{ relative }}/assets/favicons/safari-pinned-tab.svg" color="#46ad83">
|
|
||||||
<link rel="shortcut icon" href="{{ relative }}/assets/favicons/favicon.ico">
|
|
||||||
<meta name="msapplication-TileColor" content="#2b5797">
|
|
||||||
<meta name="msapplication-config" content="{{ relative }}/assets/favicons/browserconfig.xml">
|
|
||||||
<meta name="theme-color" content="#ffffff">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<img src="{{ relative }}/assets/images/cloud.svg" class="clouds" id="cloud1" />
|
|
||||||
<img src="{{ relative }}/assets/images/cloud.svg" class="clouds" id="cloud2" />
|
|
||||||
<div class="top_bg"></div>
|
|
||||||
<div class="bottom_bg"></div>
|
|
||||||
<div class="top">
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="content">
|
|
||||||
<h1><a href="{{relative}}">Redacted Life</a></h1>
|
|
||||||
{% block left_content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bottom">
|
|
||||||
<div class="wrapper">
|
|
||||||
<div class="content">
|
|
||||||
<a class="button" href="{{relative}}/subscribe/">Subscribe</a>
|
|
||||||
<a class="button" href="mailto:hello@redacted.life">Contact</a>
|
|
||||||
<a class="button" href="{{relative}}/archives/">Archives</a>
|
|
||||||
</div>
|
|
||||||
<a class="donate-link" href="{{relative}}/donate/">Consider supporting these individuals and organisations</a>
|
|
||||||
<footer>
|
|
||||||
Designed by <a href="https://webionite.com">Ceda EI</a><br />
|
|
||||||
Source available on <a href="https://git.webionite.com/ceda_ei/redacted.life">Webionite</a><br />
|
|
||||||
Licensed under <a href="https://git.webionite.com/ceda_ei/redacted.life/src/branch/master/LICENSE">MIT</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="{{ relative }}/assets/plyr/plyr.js"></script>
|
|
||||||
<script>
|
|
||||||
const player = new Plyr("#player", {
|
|
||||||
iconUrl: "{{ relative }}/assets/plyr/plyr.svg",
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Donate | Redacted Life{% endblock %}
|
|
||||||
{% block left_content %}
|
|
||||||
<ul class="donate">
|
|
||||||
{% for donate in donations %}
|
|
||||||
<li><a href="{{ donate.link }}">{{ donate.text }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
29
index.html
29
index.html
@@ -1,12 +1,17 @@
|
|||||||
{% extends "base.html" %}
|
<!DOCTYPE html>
|
||||||
{% block left_content %}
|
<html lang="en">
|
||||||
<div class="player">
|
<head>
|
||||||
<video id="player" poster="{{ thumbnail_src }}" controls>
|
<meta charset="UTF-8">
|
||||||
<source src="{{ video_src }}" type="video/mp4" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
</video>
|
<title>Redacted Life</title>
|
||||||
</div>
|
<link href="assets/css/index.css" rel="stylesheet" type="text/css">
|
||||||
<details class="shownotes">
|
</head>
|
||||||
<summary>Show Notes</summary>
|
<body>
|
||||||
{{ show_notes }}
|
<img src="assets/images/cloud.svg" class="clouds" id="cloud1" />
|
||||||
</details>
|
<img src="assets/images/cloud.svg" class="clouds" id="cloud2" />
|
||||||
{% endblock %}
|
<div class="top">
|
||||||
|
</div>
|
||||||
|
<div class="bottom">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
393
nova.py
393
nova.py
@@ -1,393 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# pylint: disable=logging-format-interpolation
|
|
||||||
"Creates a static site for redacted.life"
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
from collections import UserList
|
|
||||||
from datetime import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import os.path as path
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import markdown
|
|
||||||
from mutagen.mp3 import MP3
|
|
||||||
|
|
||||||
|
|
||||||
def gen_name(date, slug):
|
|
||||||
"Returns to file name"
|
|
||||||
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, url, data, output, template, archives):
|
|
||||||
super().__init__(data)
|
|
||||||
self.url = url
|
|
||||||
self.output = output
|
|
||||||
self.template = template
|
|
||||||
self.archives = archives
|
|
||||||
logging.info(f"New EpisodeList: {url=} {output=} {template=} {archives=}")
|
|
||||||
|
|
||||||
def sort(self, *_args, **_kwargs):
|
|
||||||
"Sorts the EpisodeList"
|
|
||||||
super().sort(key=lambda x: x.date, reverse=False)
|
|
||||||
|
|
||||||
def generate_thumbnails(self):
|
|
||||||
"Generates thumbnails for all the videos"
|
|
||||||
logging.info(f"Creating missing directories")
|
|
||||||
if not path.isdir(self.output + "assets"):
|
|
||||||
os.mkdir(self.output + "assets")
|
|
||||||
if not path.isdir(self.output + "assets/thumbnails"):
|
|
||||||
os.mkdir(self.output + "assets/thumbnails")
|
|
||||||
for episode in self.data:
|
|
||||||
location = (self.output + "assets/thumbnails/" +
|
|
||||||
gen_name(episode.date, episode.slug) + ".jpg")
|
|
||||||
logging.info(f"Creating thumbnail for {episode=} at {location}")
|
|
||||||
episode.store_thumbnail(location)
|
|
||||||
|
|
||||||
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")):
|
|
||||||
logging.info(f"Writing header for {ext}")
|
|
||||||
s.write(header)
|
|
||||||
for ep in self.data:
|
|
||||||
logging.info(f"Writing item for episode {ep} with {ext=}")
|
|
||||||
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/audio/{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")
|
|
||||||
logging.info(f"Writing end for {ext}")
|
|
||||||
s.write("</channel>")
|
|
||||||
s.write("\n")
|
|
||||||
s.write("</rss>")
|
|
||||||
|
|
||||||
def generate_archives(self):
|
|
||||||
"Generates archives page"
|
|
||||||
if not path.isdir(self.output + "archives"):
|
|
||||||
logging.info("Creating directory archives")
|
|
||||||
os.mkdir(self.output + "archives")
|
|
||||||
with open(self.output + "archives/index.html", "w") as file:
|
|
||||||
episodes = [{
|
|
||||||
"slug": gen_name(i.date, i.slug) + ".html",
|
|
||||||
"title": i.title
|
|
||||||
} for i in self.data[::-1]]
|
|
||||||
file.write(self.archives.render(episodes=episodes,
|
|
||||||
title="Archives",
|
|
||||||
relative=".."
|
|
||||||
))
|
|
||||||
|
|
||||||
def generate_site(self, root):
|
|
||||||
"Generates the entire site"
|
|
||||||
logging.info("Generating CSS from SCSS")
|
|
||||||
subprocess.run(["sass", "--update", f"{root}scss:{root}assets/css"],
|
|
||||||
check=True)
|
|
||||||
logging.info("Copy the existing assets")
|
|
||||||
shutil.copytree(root + "assets", self.output + "assets",
|
|
||||||
dirs_exist_ok=True)
|
|
||||||
logging.info("Create the required directories")
|
|
||||||
paths = [
|
|
||||||
"assets",
|
|
||||||
"assets/audio",
|
|
||||||
"assets/videos",
|
|
||||||
]
|
|
||||||
logging.info("Creating missing directories")
|
|
||||||
for directory in paths:
|
|
||||||
if not path.isdir(self.output + directory):
|
|
||||||
logging.info(f"Creating directory {directory}")
|
|
||||||
os.mkdir(self.output + directory)
|
|
||||||
|
|
||||||
logging.info("Render episodes and copy data")
|
|
||||||
for episode in self.data:
|
|
||||||
logging.info(f"Rendering episode {episode}")
|
|
||||||
html = f"{self.output}{gen_name(episode.date, episode.slug)}.html"
|
|
||||||
thumbnail = ("assets/thumbnails/" +
|
|
||||||
gen_name(episode.date, episode.slug) + ".jpg")
|
|
||||||
video = (self.output + "assets/videos/" +
|
|
||||||
gen_name(episode.date, episode.slug) + ".mp4")
|
|
||||||
audio = (self.output + "assets/audio/" +
|
|
||||||
gen_name(episode.date, episode.slug) + ".mp3")
|
|
||||||
logging.info(f"Copying {episode.video} to {video}")
|
|
||||||
shutil.copy2(episode.video, video)
|
|
||||||
logging.info(f"Copying {episode.audio}.mp3 to {audio}")
|
|
||||||
shutil.copy2(episode.audio + ".mp3", audio)
|
|
||||||
logging.info(f"Copying {episode.audio}.ogg to {audio}")
|
|
||||||
shutil.copy2(episode.audio + ".ogg", audio)
|
|
||||||
logging.info(f"Writing to {html}")
|
|
||||||
with open(html, "w") as file:
|
|
||||||
file.write(episode.render(self.template, thumbnail))
|
|
||||||
|
|
||||||
last = self.data[-1]
|
|
||||||
last_name = f"{self.output}{gen_name(last.date, last.slug)}.html"
|
|
||||||
logging.info(f"Copying last one ({last}) to index.html")
|
|
||||||
shutil.copy2(last_name, self.output + "index.html")
|
|
||||||
|
|
||||||
|
|
||||||
class Episode:
|
|
||||||
"Represents one episode of podcast"
|
|
||||||
def __init__(self, date, slug, title, show_notes, video_src, audio_src, config):
|
|
||||||
self.date = date
|
|
||||||
self.slug = slug
|
|
||||||
self.title = title
|
|
||||||
self.show_notes = markdown.markdown(show_notes)
|
|
||||||
self.video = video_src
|
|
||||||
self.audio = audio_src
|
|
||||||
self.config = config
|
|
||||||
self.length = MP3(audio_src + ".mp3").info.length
|
|
||||||
logging.info(f"New episode: {date=} {slug=} {title=} {self.video=} "
|
|
||||||
f"{self.audio=} {config=} {self.length=} {self.show_notes=}")
|
|
||||||
|
|
||||||
def render(self, template, thumbnail_src, relative="."):
|
|
||||||
"Renders the Episode with the given template"
|
|
||||||
return template.render(
|
|
||||||
title=self.title,
|
|
||||||
show_notes=jinja2.Markup(self.show_notes),
|
|
||||||
thumbnail_src=thumbnail_src,
|
|
||||||
relative=relative,
|
|
||||||
video_src=f"assets/videos/{path.basename(self.video)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def store_thumbnail(self, location):
|
|
||||||
"Stores the thumbnail for given image at path"
|
|
||||||
args = ["ffmpeg", "-i", self.video, "-ss", "00:00:01.000", "-vframes",
|
|
||||||
"1", location]
|
|
||||||
logging.info(f"Running {' '.join(args)}")
|
|
||||||
subprocess.run(args, check=False)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return int(self.length)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.slug}: {self.title}"
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self)
|
|
||||||
|
|
||||||
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")
|
|
||||||
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose Logging")
|
|
||||||
args = parser.parse_args()
|
|
||||||
input_dir = path.abspath(args.input_dir.rstrip("/")) + "/"
|
|
||||||
output_dir = path.abspath(args.output_dir.rstrip("/")) + "/"
|
|
||||||
if args.verbose:
|
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
|
||||||
else:
|
|
||||||
logging.basicConfig()
|
|
||||||
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=("tags")):
|
|
||||||
"Parses a file"
|
|
||||||
config = {}
|
|
||||||
kv_re = re.compile(r"(?P<key>\w+):\s*(?P<value>.*)")
|
|
||||||
while line := file.readline():
|
|
||||||
if line.rstrip("\n") == "---":
|
|
||||||
break
|
|
||||||
if line.strip() == "":
|
|
||||||
continue
|
|
||||||
if match := kv_re.match(line):
|
|
||||||
if match.group("key").strip().lower() in array_keys:
|
|
||||||
config[match.group("key")] = [i.strip() for i in
|
|
||||||
match.group("value").split(",")]
|
|
||||||
else:
|
|
||||||
config[match.group("key")] = match.group("value").strip()
|
|
||||||
else:
|
|
||||||
raise ParseError(f"Invalid line {line}")
|
|
||||||
|
|
||||||
return (config, file.read())
|
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
|
||||||
"Main method"
|
|
||||||
root = path.dirname(sys.argv[0]).rstrip("/") + "/"
|
|
||||||
input_dir, output_dir, url = args
|
|
||||||
logging.info(f"Input directory: {input_dir}")
|
|
||||||
logging.info(f"Output directory: {output_dir}")
|
|
||||||
logging.info(f"URL: {url}")
|
|
||||||
|
|
||||||
# Input validation
|
|
||||||
paths = [
|
|
||||||
input_dir,
|
|
||||||
input_dir + "md",
|
|
||||||
input_dir + "videos",
|
|
||||||
input_dir + "audio",
|
|
||||||
]
|
|
||||||
logging.info("Checking if all paths exist.")
|
|
||||||
if not all(path.isdir((fail := i)) for i in paths):
|
|
||||||
logging.error(f"Invalid Input. {fail} is not a directory.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
logging.info("Creating output directory if it doesn't exist")
|
|
||||||
if not path.isdir(output_dir):
|
|
||||||
os.mkdir(output_dir)
|
|
||||||
|
|
||||||
env = jinja2.Environment(
|
|
||||||
loader=jinja2.FileSystemLoader(root),
|
|
||||||
autoescape=jinja2.select_autoescape("html")
|
|
||||||
)
|
|
||||||
|
|
||||||
logging.info("Creating EpisodeList")
|
|
||||||
podcast = EpisodeList(
|
|
||||||
url,
|
|
||||||
[],
|
|
||||||
output_dir,
|
|
||||||
env.get_template("index.html"),
|
|
||||||
env.get_template("archives.html")
|
|
||||||
)
|
|
||||||
|
|
||||||
split = re.compile(r"((?P<date>\d{4}-[01]?\d-[0123]?\d)-(?P<slug>.*).md)")
|
|
||||||
logging.info(f"Parsing all files in {input_dir}md")
|
|
||||||
for file in os.listdir(input_dir + "md"):
|
|
||||||
logging.info(f"File: {file}")
|
|
||||||
match = split.match(file)
|
|
||||||
logging.info(f"Match: {match}")
|
|
||||||
if not match:
|
|
||||||
logging.error(f"Invalid filename: {file}")
|
|
||||||
continue
|
|
||||||
date = datetime.strptime(match.group("date"), "%Y-%m-%d")
|
|
||||||
logging.info(f"Date: {date}")
|
|
||||||
slug = match.group("slug")
|
|
||||||
logging.info(f"Slug: {slug}")
|
|
||||||
with open(input_dir + "md/" + file) as episode:
|
|
||||||
try:
|
|
||||||
config, show_notes = parse_file(episode)
|
|
||||||
logging.info(f"Config: {config}")
|
|
||||||
logging.info(f"Show Notes: {show_notes}")
|
|
||||||
except ParseError as err:
|
|
||||||
logging.error(f"Error while parsing file: {file}")
|
|
||||||
logging.error(err)
|
|
||||||
return 2
|
|
||||||
logging.info("Appending to EpisodeList")
|
|
||||||
podcast.append(
|
|
||||||
Episode(
|
|
||||||
date,
|
|
||||||
slug,
|
|
||||||
config["title"],
|
|
||||||
show_notes,
|
|
||||||
input_dir + "videos/" + gen_name(date, slug) + ".mp4",
|
|
||||||
input_dir + "audio/" + gen_name(date, slug),
|
|
||||||
config
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not path.isdir(output_dir + "subscribe"):
|
|
||||||
os.mkdir(output_dir + "subscribe")
|
|
||||||
logging.info("Generating subscribe page")
|
|
||||||
with open(input_dir + "subscribe.json") as subscribe, \
|
|
||||||
open(output_dir + "subscribe/index.html", "w") as html:
|
|
||||||
html.write(env.get_template("subscribe.html").render(
|
|
||||||
relative="..",
|
|
||||||
subscriptions=json.load(subscribe)
|
|
||||||
))
|
|
||||||
|
|
||||||
if not path.isdir(output_dir + "donate"):
|
|
||||||
os.mkdir(output_dir + "donate")
|
|
||||||
logging.info("Generating donate page")
|
|
||||||
with open(input_dir + "donate.json") as donate, \
|
|
||||||
open(output_dir + "donate/index.html", "w") as html:
|
|
||||||
html.write(env.get_template("donate.html").render(
|
|
||||||
relative="..",
|
|
||||||
donations=json.load(donate)
|
|
||||||
))
|
|
||||||
|
|
||||||
logging.info("Sorting podcasts")
|
|
||||||
podcast.sort()
|
|
||||||
logging.info("Generating thumbnails")
|
|
||||||
podcast.generate_thumbnails()
|
|
||||||
logging.info("Generating archives pages")
|
|
||||||
podcast.generate_archives()
|
|
||||||
logging.info("Generating RSS feeds")
|
|
||||||
with open(input_dir + "header.rss") as header:
|
|
||||||
podcast.generate_rss(header.read())
|
|
||||||
logging.info("Generating Site")
|
|
||||||
podcast.generate_site(root)
|
|
||||||
logging.info("Copying Overrides")
|
|
||||||
shutil.copytree(input_dir + "overrides", output_dir, dirs_exist_ok=True)
|
|
||||||
logging.info("Done")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main(parse_args()))
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
jinja2
|
|
||||||
markdown
|
|
||||||
mutagen
|
|
||||||
@@ -1,23 +1,28 @@
|
|||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Comfortaa';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
src: url(../fonts/Lato-Regular.ttf);
|
src: url(./Comfortaa-Latin-Light.woff) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
/* latin */
|
/* latin */
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Montserrat';
|
font-family: 'Comfortaa';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: swap;
|
font-weight: 700;
|
||||||
src: url(../fonts/Montserrat-Bold.ttf);
|
font-display: swap;
|
||||||
|
src: url(./Comfortaa-Latin.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin body-font {
|
@mixin body-font {
|
||||||
font-family: "Lato", sans-serif;
|
font-family: "Comfortaa", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin title-font {
|
@mixin title-font {
|
||||||
font-family: "Montserrat", sans-serif;
|
font-family: "Comfortaa", sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|||||||
273
scss/index.scss
273
scss/index.scss
@@ -1,22 +1,19 @@
|
|||||||
$cloud_width: 200px;
|
$cloud_width: 200px;
|
||||||
$top-height: 70vh;
|
|
||||||
$top-height-phone: 64vh;
|
|
||||||
|
|
||||||
@import 'fonts';
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow-x: hidden;
|
.top {
|
||||||
|
background-image: linear-gradient(#24d3ff, #aaeeff);
|
||||||
|
height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
@include body-font;
|
.bottom {
|
||||||
|
background-image: linear-gradient(#26a4c4, #afdde9);
|
||||||
|
height: 30vh;
|
||||||
|
}
|
||||||
.clouds {
|
.clouds {
|
||||||
z-index: 50;
|
position: absolute;
|
||||||
position: fixed;
|
|
||||||
width: $cloud_width;
|
width: $cloud_width;
|
||||||
animation-name: move_right;
|
animation-name: move_right;
|
||||||
animation-direction: alternate;
|
animation-direction: alternate;
|
||||||
@@ -36,261 +33,9 @@ body {
|
|||||||
animation-duration: 15s;
|
animation-duration: 15s;
|
||||||
transform: rotateY(180deg);
|
transform: rotateY(180deg);
|
||||||
}
|
}
|
||||||
.top_bg {
|
|
||||||
background-image: linear-gradient(#24d3ff, #aaeeff);
|
|
||||||
height: $top-height;
|
|
||||||
width: 100vw;
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
.bottom_bg {
|
|
||||||
background-image: linear-gradient(#26a4c4, #afdde9);
|
|
||||||
height: 100vh - $top-height;
|
|
||||||
width: 100vw;
|
|
||||||
position: fixed;
|
|
||||||
top: $top-height;
|
|
||||||
}
|
|
||||||
.top {
|
|
||||||
min-height: $top-height;
|
|
||||||
display: flex;
|
|
||||||
align-items: end;
|
|
||||||
.wrapper {
|
|
||||||
min-height: $top-height;
|
|
||||||
align-items: flex-end;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
.player {
|
|
||||||
margin: auto;
|
|
||||||
width: 80%;
|
|
||||||
.plyr {
|
|
||||||
border-radius: 1em;
|
|
||||||
border-color: #000;
|
|
||||||
border-width: 4px;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.shownotes {
|
|
||||||
padding: 2em 1em 0;
|
|
||||||
margin: auto;
|
|
||||||
width: 80%;
|
|
||||||
a {
|
|
||||||
color: #292929;
|
|
||||||
padding: 0.3em;
|
|
||||||
border-radius: 1.6em;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #FFF;
|
|
||||||
background-color: #000;
|
|
||||||
text-decoration: none;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ul.archives, ul.donate {
|
|
||||||
list-style-type: none;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
text-align: center;
|
|
||||||
li {
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #000;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
a:hover, a:active {
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
background-color: #000;
|
|
||||||
color: #FFF;
|
|
||||||
padding: 0.3em;
|
|
||||||
border-radius: 1.6em;
|
|
||||||
}
|
|
||||||
margin: 0.5em 1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscribe {
|
|
||||||
display: flex;
|
|
||||||
width: 600px;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: auto;
|
|
||||||
.subscription {
|
|
||||||
width: 200px;
|
|
||||||
color: #000;
|
|
||||||
text-align: center;
|
|
||||||
transition-duration: 0.2em;
|
|
||||||
padding: 1rem 0;
|
|
||||||
span {
|
|
||||||
color: inherit;
|
|
||||||
font-size: 100px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
@include title-font;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.subscription:hover, .subscription:active {
|
|
||||||
text-decoration: none;
|
|
||||||
transition-duration: 0.2em;
|
|
||||||
background-color: #000;
|
|
||||||
color: #FFF;
|
|
||||||
border-radius: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom {
|
|
||||||
min-height: 95vh - $top-height;
|
|
||||||
margin-bottom: 5vh;
|
|
||||||
.wrapper {
|
|
||||||
min-height: 95vh - $top-height;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
flex-direction: column;
|
|
||||||
padding-top: 1em;
|
|
||||||
height: 100%;
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
.button {
|
|
||||||
font-size: 1.5em;
|
|
||||||
color: #000;
|
|
||||||
padding: 0.5em;
|
|
||||||
border-color: #000;
|
|
||||||
border-width: 3px;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 1.5em;
|
|
||||||
text-decoration: none;
|
|
||||||
transition-duration: 0.4s;
|
|
||||||
}
|
|
||||||
.button:hover, .button:active {
|
|
||||||
transition-duration: 0.4s;
|
|
||||||
background-color: #000;
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a.donate-link {
|
|
||||||
margin: 1em 0;
|
|
||||||
color: #292929;
|
|
||||||
padding: 0.3em;
|
|
||||||
border-radius: 1.6em;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #FFF;
|
|
||||||
background-color: #000;
|
|
||||||
text-decoration: none;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
margin-top: 1em;
|
|
||||||
padding: 0 1em;
|
|
||||||
text-align: center;
|
|
||||||
color: #292929;
|
|
||||||
a {
|
|
||||||
color: #292929;
|
|
||||||
padding: 0.3em;
|
|
||||||
border-radius: 1.6em;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: #FFF;
|
|
||||||
background-color: #000;
|
|
||||||
text-decoration: none;
|
|
||||||
transition-duration: 0.2s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100vw;
|
|
||||||
box-sizing: border-box;
|
|
||||||
z-index: 100;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
.content {
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 800px;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
|
||||||
@include title-font;
|
|
||||||
font-size: 4em;
|
|
||||||
text-align: center;
|
|
||||||
a {
|
|
||||||
color: #000;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
body {
|
|
||||||
.top {
|
|
||||||
.wrapper {
|
|
||||||
.subscribe {
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
body {
|
|
||||||
.top_bg {
|
|
||||||
height: $top-height-phone;
|
|
||||||
}
|
|
||||||
.bottom_bg {
|
|
||||||
height: 100vh - $top-height-phone;
|
|
||||||
top: $top-height-phone;
|
|
||||||
}
|
|
||||||
.top {
|
|
||||||
min-height: $top-height-phone;
|
|
||||||
.wrapper {
|
|
||||||
min-height: $top-height-phone;
|
|
||||||
.player {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.shownotes {
|
|
||||||
width: 90%;
|
|
||||||
padding: 2em 0 1em 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bottom {
|
|
||||||
min-height: 95vh - $top-height-phone;
|
|
||||||
.wrapper {
|
|
||||||
min-height: 95vh - $top-height-phone;
|
|
||||||
.content {
|
|
||||||
.button {
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 3em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
body {
|
|
||||||
.top {
|
|
||||||
.wrapper {
|
|
||||||
.subscribe {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes move_right {
|
@keyframes move_right {
|
||||||
from {left: 0}
|
from {left: 0}
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Subscribe | Redacted Life{% endblock %}
|
|
||||||
{% block stylesheets %}
|
|
||||||
<link href="{{relative}}/assets/fork-awesome/css/fork-awesome.min.css" rel="stylesheet" type="text/css">
|
|
||||||
{% endblock %}
|
|
||||||
{% block left_content %}
|
|
||||||
<div class="subscribe">
|
|
||||||
{% for subscription in subscriptions %}
|
|
||||||
<a href={{subscription.link}} class="subscription">
|
|
||||||
<span class="fa fa-{{subscription.icon}}">
|
|
||||||
<p>{{subscription.text}}</p>
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
Reference in New Issue
Block a user