diff --git a/data.py b/data.py
index 012deff..80d04da 100644
--- a/data.py
+++ b/data.py
@@ -23,6 +23,7 @@ class Podcast(BaseModel):
name: str
description: str
explicit: bool
+ image_filename: Optional[str] = Field(default=None)
episodes: List[Episode] = list()
diff --git a/main.py b/main.py
index c5641a1..32aca1c 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,5 @@
import urllib.parse
+import uuid
from datetime import timedelta
from pathlib import Path
from typing import Annotated, Optional
@@ -9,6 +10,7 @@ from fastapi import FastAPI, Form, HTTPException, Request, Response, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
+from PIL import Image
import data
from process import AudioProcessor
@@ -231,6 +233,7 @@ def admin_edit_feed_post(
feed_id: str,
name: Annotated[str, Form()],
description: Annotated[str, Form()],
+ image: Optional[UploadFile] = None,
):
repo = data.load_repository()
@@ -244,6 +247,34 @@ def admin_edit_feed_post(
new_feed.description = description
+ if image is not None and image.size > 0:
+ if not (image.filename.endswith(".jpg") or image.filename.endswith(".png")):
+ raise HTTPException(
+ status_code=400,
+ detail="The uploaded podcast image must be a jpg or png",
+ )
+
+ im = Image.open(image.file)
+
+ if im.size[0] != im.size[1] or im.size[0] < 1400 or im.size[0] > 3000:
+ raise HTTPException(
+ status_code=400,
+ detail="The uploaded podcast image must be square and between 1400x1400px and 3000x3000px in size",
+ )
+
+ if im.mode != "RGB":
+ raise HTTPException(
+ status_code=400,
+ detail="The uploaded podcast image must be in RGB format",
+ )
+
+ if new_feed.image_filename is not None:
+ (settings.directory / new_feed.image_filename).unlink(missing_ok=True)
+
+ filename = f"img_{uuid.uuid4()}" + Path(image.filename).suffix
+ im.save(settings.directory / filename)
+ new_feed.image_filename = filename
+
repo.podcasts[feed_id] = new_feed
data.save_repository(repo)
@@ -264,6 +295,12 @@ def get_feed(request: Request, feed_id: str):
podcast = podgen.Podcast(
name=feed.name,
description=feed.description,
+ image=urllib.parse.urljoin(
+ str(request.base_url),
+ f"/{feed_id}/{feed.image_filename}",
+ )
+ if feed.image_filename is not None
+ else None,
website=urllib.parse.urljoin(str(request.base_url), feed_id),
explicit=feed.explicit,
feed_url=str(request.url),
@@ -291,10 +328,8 @@ def get_feed(request: Request, feed_id: str):
return Response(content=podcast.rss_str(), media_type="application/xml")
-@app.get("/{feed_id}/{episode_id}")
-def get_episode(feed_id: str, episode_id: str):
- episode_id = episode_id.removesuffix(".m4a")
-
+@app.get("/{feed_id}/{filename}")
+def get_episode_or_cover(feed_id: str, filename: str):
repo = data.load_repository()
feed = next(
(podcast for id, podcast in repo.podcasts.items() if id == feed_id), None
@@ -303,11 +338,24 @@ def get_episode(feed_id: str, episode_id: str):
if feed is None:
raise HTTPException(status_code=404, detail="Podcast not found")
- episode = next(
- (episode for episode in feed.episodes if episode.id == episode_id), None
- )
+ if filename.endswith(".m4a"):
+ episode = next(
+ (
+ episode
+ for episode in feed.episodes
+ if episode.id == filename.removesuffix(".m4a")
+ ),
+ None,
+ )
- if episode is None:
- raise HTTPException(status_code=404, detail="Episode not found")
+ if episode is None:
+ raise HTTPException(status_code=404, detail="Episode not found")
- return FileResponse(settings.directory / f"{episode_id}.m4a")
+ return FileResponse(settings.directory / f"{episode.id}.m4a")
+
+ elif (
+ filename.endswith(".jpg") or filename.endswith(".png")
+ ) and filename == feed.image_filename:
+ return FileResponse(settings.directory / feed.image_filename)
+
+ return HTTPException(status_code=404, detail="File not found")
diff --git a/pyproject.toml b/pyproject.toml
index 51741cd..9f2e451 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,6 +9,7 @@ dependencies = [
"fastapi[standard]>=0.115.6",
"ffmpeg-normalize>=1.31.0",
"ffmpeg-python>=0.2.0",
+ "pillow>=11.1.0",
"podgen>=1.1.0",
"pydantic>=2.10.5",
"pydantic-settings>=2.7.1",
diff --git a/templates/admin_feed.html.j2 b/templates/admin_feed.html.j2
index 88f0c3f..14e8528 100644
--- a/templates/admin_feed.html.j2
+++ b/templates/admin_feed.html.j2
@@ -1,14 +1,28 @@
{% extends 'layout.html.j2' %}
{% block content %}
+{% if feed.image_filename %}
+
+
+{% endif %}
Description: {{ feed.description }}
- Subscribe at: + Actions: + Edit
++ Description: + {{ feed.description }} +
++ Subscribe:
{{ feed_uri }}
-