Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
on:
pull_request:
branches-ignore:
- revamp
push:
branches: [main]
jobs:
test-appu2:
if: github.event_name == 'pull_request' && contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build Appu2 Docker image
run: docker build --pull -t appu2 .
- name: Run tests in Docker container
run: docker run --rm -v ${PWD}/tests:/home/appu/tests appu2 uv run pytest tests
pytest:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -16,7 +24,7 @@ jobs:
- name: Run tests in Docker container
run: docker run --rm appu /bin/sh -c 'pip install -r dev-requirements.txt; python -m pytest'
test:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -36,7 +44,7 @@ jobs:
with:
path-to-profile: profile.cov
check-formatting:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -45,7 +53,7 @@ jobs:
run: |
gofmt -s -e -d -l . | tee /tmp/gofmt.output && [ $(cat /tmp/gofmt.output | wc -l) -eq 0 ]
check-smells:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -61,7 +69,7 @@ jobs:
run: |
go vet ./...
check-complexity:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -77,7 +85,7 @@ jobs:
run: |
gocyclo -over 15 .
check-style:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -93,7 +101,7 @@ jobs:
run: |
golint ./...
check-ineffectual-assignments:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -112,7 +120,7 @@ jobs:
run: |
ineffassign ./...
check-spelling:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -128,7 +136,7 @@ jobs:
run: |
misspell -error .
staticcheck:
if: github.event_name == 'pull_request' && github.head_ref != 'revamp'
if: github.event_name == 'pull_request' && ! contains(github.head_ref, 'revamp')
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM python:3.13-slim-bookworm

ENV PYTHONWARNINGS=ignore::SyntaxWarning

VOLUME /home/appu/.aws

RUN apt update \
&& apt upgrade -y \
&& apt install -y \
ffmpeg

COPY --from=ghcr.io/astral-sh/uv:0.5.26 /uv /uvx /bin

RUN addgroup --gid 1000 appu \
&& adduser --uid 1000 --ingroup appu appu

USER appu

COPY --chown=appu:appu \
pyproject.toml \
src/ \
uv.lock \
/home/appu

RUN cd /home/appu/ \
&& /bin/uv sync

WORKDIR /home/appu

CMD ["/bin/uv", "run", "appu"]
73 changes: 71 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,40 @@ This is the new version of appu, it's a work in progress.

### Pre-requisites

- Make sure you have `uv` [installed in your system](https://docs.astral.sh/uv/getting-started/installation/).
Use your favorite package manager to install it.
Make sure you have installed in your system:

- `ffmpeg` ([download](https://www.ffmpeg.org/download.html))
- `uv` ([installation](https://docs.astral.sh/uv/getting-started/installation/))

Alternatively, you can use [APPU 2 on Docker](#use-docker-(optional)), so you'll need it.

#### Automation of `venv` management (optional)

You can use `mise` to transparently manage your venvs. Installation instructions are [here](https://mise.jdx.dev).
With `mise` installed, you just need to trust the source directory, by running `mise trust` from within the cloned repository.

#### Use Docker (optional)

You can use Docker to run APPU 2 without changing your environment.

To build the Docker image:

```bash
docker build -t appu2 .
```

To run APPU 2:

```bash
docker run --rm -v ${HOME}/mypodcast:/home/appu/mypodcast appu2 appu /home/appu/mypodcast/episode.yaml
```

To run APPU 2's tests:

```bash
docker run --rm -v ${PWD}/tests:/home/appu/tests appu2 uv run pytests /home/appu/tests
```

### Install deps

Run `uv sync` to install the python dependencies.
Expand All @@ -24,10 +50,53 @@ Run `uv sync` to install the python dependencies.

Run `uv run pre-commit install` to install the pre-commit hook. (The first time you commit, it will take some time.)

### Run the tests

Run `uv run pytest tests` to run the tests for APPU 2, which should output something similar to the following:
```
============================= test session starts ==============================
platform linux -- Python 3.13.2, pytest-8.3.5, pluggy-1.5.0
rootdir: /home/appu
configfile: pyproject.toml
collected 7 items

tests/audio_test.py ... [ 42%]
tests/episode_test.py . [ 57%]
tests/remote_test.py ... [100%]

============================== 7 passed in 2.18s ===============================
```

### Run the app

Run `uv run appu` to run the app, or if you have done the right thing before, you might just be able to run `appu` directly with no parameters.

#### Run the MP3 episode edition with appu2

Considering you've run epidator the usual way, you can now run appu with `uv` and the episode.yaml file to get the old appu's feature:

```bash
uv run appu appu ~/mypodcast/episode.yaml
```

This is a sample of the output:

```bash
2025-03-09 03:59:18.438 | INFO | appu2.logging:89 - Logging configured successfully
2025-03-09 03:59:18.438 | INFO | appu2.cli:37 - debug=False
2025-03-09 03:59:18.438 | INFO | appu2.cli:38 - Loading episode /home/user/mypodcast/episode.yaml config
2025-03-09 03:59:18.457 | INFO | logging:1736 - [botocore.credentials] Found credentials in shared credentials file: ~/.aws/credentials
2025-03-09 03:59:18.548 | INFO | appu2.remote:46 - Downloading s3://mypodcast-episodes-bucket/masters/episode.master.mp3 (Direct S3)
2025-03-09 03:59:23.668 | INFO | appu2.cli:41 - Normalizing master audio
2025-03-09 03:59:29.027 | INFO | appu2.remote:71 - Downloading https://my.podcast.com/intro.mp3?dl=0 (regular HTTP)
2025-03-09 03:59:32.527 | INFO | appu2.remote:71 - Downloading https://my.podcast.com/cover.png?dl=1 (regular HTTP)
2025-03-09 03:59:34.378 | INFO | appu2.cli:45 - Extract intro and outro
2025-03-09 03:59:35.170 | INFO | appu2.cli:47 - Concatenating intro, master audio, and outro
2025-03-09 03:59:38.919 | INFO | appu2.cli:49 - Exporting track
2025-03-09 04:00:21.976 | INFO | appu2.cli:60 - Uploading track
2025-03-09 04:00:21.996 | INFO | appu2.remote:18 - Uploading episode.mp3 to s3://mypodcast-episodes-bucket/podcast/episode.mp3
```

## Rationale

While running the [Entre Dev y Ops podcast](https://www.entredevyops.es), the authors found interesting to start building a set of tools to make this easier. We hope this might help anyone else.
Expand Down
16 changes: 15 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ description = "Automatic Podcast PUblisher, aka appu, is a toolkit for podcast e
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"audioop-lts>=0.2.1",
"boto3>=1.37.9",
"loguru>=0.7.3",
"pydantic>=2.10.6",
"pydantic-settings>=2.7.1",
"pydub>=0.25.1",
"requests>=2.32.3",
"typer>=0.15.1",
]

Expand All @@ -18,7 +22,11 @@ appu = "appu2.cli:app"
package = true

[dependency-groups]
dev = ["pre-commit>=4.1.0", "ruff>=0.9.4"]
dev = [
"pre-commit>=4.1.0",
"pytest>=8.3.5",
"ruff>=0.9.4",
]

[tool.ruff]
# Excludes the old appu python code from the linting
Expand All @@ -41,3 +49,9 @@ extend-select = [
[tool.ruff.lint.isort]
combine-as-imports = true
lines-after-imports = 2

[tool.ruff.lint.per-file-ignores]
# Excludes some rules for test files
"tests/**/*.py" = [
"S101", # allows asserts in tests
]
33 changes: 33 additions & 0 deletions src/appu2/audio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pydub


def normalize(filename: str) -> pydub.AudioSegment:
"""
This function normalizes track.
"""
audio = pydub.AudioSegment.from_mp3(filename)

return pydub.effects.normalize(audio, headroom=-1.5)


def split(filename: str, *slices_limits: list[str]) -> list[pydub.AudioSegment]:
"""
This function splits track into as many slices as defined.
"""
audio = pydub.AudioSegment.from_mp3(filename)
slices = []
for slice_limit in slices_limits:
slices.append(audio[slice_limit])

return slices


def concat(audios):
"""
This function concatenates all the audios.
"""
final = audios[0][0]
for audio, fade in audios[1:]:
final = final.append(audio, crossfade=fade)

return final
37 changes: 37 additions & 0 deletions src/appu2/cli.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import typer
from loguru import logger

from appu2.logging import LogLevel, setup_logging

from .audio import concat, normalize, split
from .config import ROOT_PROJECT_PATH, settings
from .episode import load_episode
from .remote import download, upload


app = typer.Typer(help="appu CLI", no_args_is_help=True)

Expand All @@ -27,3 +32,35 @@ def app_test() -> None:
print(settings.debug)
print("Hello World!")
print(ROOT_PROJECT_PATH)


@app.command(name="appu", help="Old appu command")
def app_appu(episode_yaml) -> None:
logger.info(f"Loading episode {episode_yaml} config")
episode = load_episode(episode_yaml)

master_recording = download(episode.master)
intro_song = download(episode.intro)
cover_file = download(episode.cover)

logger.info("Normalizing master audio")
master_audio = normalize(master_recording)

logger.info("Extract intro and outro")
intro_audio, outro_audio = split(intro_song, slice(0, 20000, None), slice( -40000, None, None))

logger.info("Concatenating intro, master audio, and outro")
final_audio = concat([(intro_audio, 0), (master_audio, 1000), (outro_audio, 4000)])

logger.info("Exporting track")
final_audio.export(
episode.filename,
format="mp3",
tags=episode.tags,
bitrate="48000",
parameters=["-ac", "1"],
id3v2_version="3",
cover=cover_file,
)

upload(episode.filename, episode.destination)
1 change: 1 addition & 0 deletions src/appu2/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


__project_name__ = "appu"

ROOT_PROJECT_PATH = Path(__file__).parent.parent.parent
Expand Down
Loading