Compare commits

..

No commits in common. "2d424d0be6ad4122a77662f0fe554e4a48a363c5" and "4e89158d961474f35aacbe423af31b7c3c30a801" have entirely different histories.

7 changed files with 5 additions and 163 deletions

View file

@ -9,7 +9,6 @@ dependencies = [
"fastapi[standard]>=0.115.6", "fastapi[standard]>=0.115.6",
"ffmpeg-normalize>=1.31.0", "ffmpeg-normalize>=1.31.0",
"ffmpeg-python>=0.2.0", "ffmpeg-python>=0.2.0",
"markdown>=3.7",
"nanoid>=2.0.0", "nanoid>=2.0.0",
"pillow>=11.1.0", "pillow>=11.1.0",
"podgen>=1.1.0", "podgen>=1.1.0",

View file

@ -1,90 +0,0 @@
import argparse
import hashlib
import shutil
import sys
from pathlib import Path
import ffmpeg
import structlog
from sqlmodel import Session, select
# Add the src directory to the system path
sys.path.append(str(Path(__file__).resolve().parent.parent / "src"))
import models as models
from settings import settings
log = structlog.get_logger()
def import_episode(filename: Path, podcast_id: str, process: bool, move: bool = True):
if process:
raise NotImplementedError("Importing with processing is not implemented")
if filename.suffix != ".m4a" and not process:
log.error("Input file must be in an m4a container if not processing")
return
with Session(models.engine) as session:
podcast = session.exec(
select(models.Podcast).where(models.Podcast.id == podcast_id)
).first()
if podcast is None:
log.error("Failed importing episode, podcast does not exist.")
return
episode = models.PodcastEpisode(
name=filename.stem, file_size=0, file_hash="", podcast_id=podcast.id
)
episode_filename = settings.directory / f"{episode.id}.m4a"
if move:
log.info("Moving episode to %s...", episode_filename)
shutil.move(filename, episode_filename)
else:
log.info("Copying episode to %s...", episode_filename)
shutil.copyfile(filename, episode_filename)
probe = ffmpeg.probe(str(episode_filename))
stream = next(
(stream for stream in probe["streams"] if stream["codec_type"] == "audio"),
None,
)
file_hash = hashlib.sha256()
with open(episode_filename, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
file_hash.update(byte_block)
episode.duration = (
float(stream["duration"])
if stream is not None and "duration" in stream
else None
)
episode.file_hash = file_hash.hexdigest()
episode.file_size = episode_filename.stat().st_size
session.add(episode)
session.commit()
log.info("Imported episode as %s", episode.id)
def main():
parser = argparse.ArgumentParser(
prog="import-episode.py",
description="Import an episode",
)
parser.add_argument("filename")
parser.add_argument("podcast_id")
parser.add_argument("--process", action="store_true")
args = parser.parse_args()
import_episode(Path(args.filename), args.podcast_id, args.process)
if __name__ == "__main__":
main()

View file

@ -1,50 +0,0 @@
import argparse
import sys
from datetime import datetime
from pathlib import Path
import structlog
from sqlmodel import Session, select
# Add the src directory to the system path
sys.path.append(str(Path(__file__).resolve().parent.parent / "src"))
import models as models
log = structlog.get_logger()
def update_pub_date(episode_id: str, new_date: str):
with Session(models.engine) as session:
episode = session.exec(
select(models.PodcastEpisode).where(models.PodcastEpisode.id == episode_id)
).first()
if episode is None:
log.error("Could not find episode")
return
episode.publish_date = datetime.fromisoformat(new_date)
assert episode.publish_date.tzinfo is not None, "timezone is required"
session.add(episode)
session.commit()
log.info("Updated episode", episode.id)
def main():
parser = argparse.ArgumentParser(
prog="update-pub-date.py",
description="Update an episode publish date",
)
parser.add_argument("episode_id")
parser.add_argument("new_date")
args = parser.parse_args()
update_pub_date(args.episode_id, args.new_date)
if __name__ == "__main__":
main()

View file

@ -1,11 +1,10 @@
import urllib.parse import urllib.parse
import uuid import uuid
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import timedelta, timezone from datetime import timedelta
from pathlib import Path from pathlib import Path
from typing import Annotated, Any, Generator, Optional from typing import Annotated, Any, Generator, Optional
import markdown
import podgen import podgen
import structlog import structlog
from fastapi import Depends, FastAPI, Form, HTTPException, Request, Response, UploadFile from fastapi import Depends, FastAPI, Form, HTTPException, Request, Response, UploadFile
@ -533,7 +532,7 @@ def get_podcast_feed(session: SessionDep, request: Request, podcast_id: str):
podgen.Episode( podgen.Episode(
id=episode.id, id=episode.id,
title=episode.name, title=episode.name,
publication_date=episode.publish_date.astimezone(tz=timezone.utc), publication_date=episode.publish_date,
media=podgen.Media( media=podgen.Media(
urllib.parse.urljoin( urllib.parse.urljoin(
str(request.base_url), f"{podcast.id}/{episode.id}.m4a" str(request.base_url), f"{podcast.id}/{episode.id}.m4a"
@ -543,9 +542,7 @@ def get_podcast_feed(session: SessionDep, request: Request, podcast_id: str):
if episode.duration is not None if episode.duration is not None
else None, else None,
), ),
long_summary=markdown.markdown(episode.description) long_summary=episode.description,
if episode.description is not None
else None,
) )
) )

View file

@ -24,7 +24,7 @@ class PodcastEpisode(SQLModel, table=True):
id: str = Field(primary_key=True, default_factory=lambda: nanoid.generate()) id: str = Field(primary_key=True, default_factory=lambda: nanoid.generate())
name: str name: str
duration: Optional[float] = Field(default=None) duration: Optional[float] = Field(default=None)
description: Optional[str] = Field(default=None) description: Optional[float] = Field(default=None)
file_hash: str file_hash: str
file_size: int file_size: int
publish_date: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) publish_date: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

View file

@ -10,10 +10,7 @@
</label> </label>
<label> <label>
Description Description
<textarea name="description" <textarea name="description">{% if episode.description %}{{ episode.description }}{% endif %}</textarea>
aria-describedby="description-help">{% if episode.description %}{{ episode.description }}{% endif %}</textarea>
<small id="description-help"><a href="https://www.markdownguide.org/cheat-sheet/">Markdown</a> is supported
for any content in here.</small>
</label> </label>
</fieldset> </fieldset>

11
uv.lock
View file

@ -341,15 +341,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 },
] ]
[[package]]
name = "markdown"
version = "3.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/28/3af612670f82f4c056911fbbbb42760255801b3068c48de792d354ff4472/markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2", size = 357086 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/08/83871f3c50fc983b88547c196d11cf8c3340e37c32d2e9d6152abe2c61f7/Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803", size = 106349 },
]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "3.0.0" version = "3.0.0"
@ -444,7 +435,6 @@ dependencies = [
{ name = "fastapi", extra = ["standard"] }, { name = "fastapi", extra = ["standard"] },
{ name = "ffmpeg-normalize" }, { name = "ffmpeg-normalize" },
{ name = "ffmpeg-python" }, { name = "ffmpeg-python" },
{ name = "markdown" },
{ name = "nanoid" }, { name = "nanoid" },
{ name = "pillow" }, { name = "pillow" },
{ name = "podgen" }, { name = "podgen" },
@ -461,7 +451,6 @@ requires-dist = [
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.115.6" },
{ name = "ffmpeg-normalize", specifier = ">=1.31.0" }, { name = "ffmpeg-normalize", specifier = ">=1.31.0" },
{ name = "ffmpeg-python", specifier = ">=0.2.0" }, { name = "ffmpeg-python", specifier = ">=0.2.0" },
{ name = "markdown", specifier = ">=3.7" },
{ name = "nanoid", specifier = ">=2.0.0" }, { name = "nanoid", specifier = ">=2.0.0" },
{ name = "pillow", specifier = ">=11.1.0" }, { name = "pillow", specifier = ">=11.1.0" },
{ name = "podgen", specifier = ">=1.1.0" }, { name = "podgen", specifier = ">=1.1.0" },