Site Map - skip to main content

Hacker Public Radio

Your ideas, projects, opinions - podcasted.

New episodes every weekday Monday through Friday.
This page was generated by The HPR Robot at


hpr3496 :: How I record HPR Episodes

Some python to record short segments of audio.

<< First, < Previous, , Latest >>

Hosted by norrist on Monday, 2021-12-27 is flagged as Clean and is released under a CC-BY-SA license.
python, sox. 3.
The show is available on the Internet Archive at: https://archive.org/details/hpr3496

Listen in ogg, spx, or mp3 format. Play now:

Duration: 00:28:27

general.

https://gitlab.com/norrist/solocast

Sample script.txt

This is a sample script for solocast.
Separate the segments with a blank line

Bulleted lists are OK, but keep the items together by not skipping a line
- Item 1
- Item 2

### Markdown Formatting is OK
But the Formatting gets lost in the script
so you can write show notes in loosely formatted markdown

Don't have more than 1 blank line separating segments

solocast.py

#! /usr/bin/env python3

import click
import os
from shutil import which

script_file = "script.txt"
recording_directory_name = "Recordings"
recording_format = "wav"
script_segments = {}


def test_sox_exists():
    try:
        assert which("sox")
    except AssertionError:
        print("Cant find sox.  Install sox somewhere in your path.")
        exit(1)


def get_recording_file_name(slug):
    return f"{recording_directory_name}/{slug}.{recording_format}"


def project_prep():
    if not os.path.exists(recording_directory_name):
        os.makedirs(recording_directory_name)
    if not os.path.exists(f"{recording_directory_name}/Archive"):
        os.makedirs(f"{recording_directory_name}/Archive")


def wait_for_input():
    click.echo("*" * 40)
    _ = input("Press ENTER to Continue")


def add_slug_text(slug, text):
    script_segments[slug] = text


def recording_exists(slug):
    if os.path.isfile(get_recording_file_name(slug)):
        return True
    return False


def noise_profile_missing():
    if os.path.isfile(f"{recording_directory_name}/noise.prof"):
        return False
    return True


def truncate_audio(slug):
    recording = get_recording_file_name(slug)
    new_recording = f"{recording_directory_name}/{slug}-truncated.{recording_format}"
    click.echo(f"truncating {recording}")

    SOX_CMD = (
        f"sox -V2 {recording}  {new_recording}   silence -l 1 0.1 .1% -1 1.0 .1%   stat"
    )
    click.echo(SOX_CMD)
    os.system(SOX_CMD)
    os.system(
        f" mv -v {recording} {recording_directory_name}/Archive/{slug}.{recording_format}"
    )
    os.rename(new_recording, recording)
    review_audio(slug)


def play_audio(slug):
    recording = get_recording_file_name(slug)
    click.echo(f"Playing {recording}")
    os.system(f"play {recording}")
    review_audio(slug)


def delete_audio(slug):
    recording = get_recording_file_name(slug)
    os.remove(recording)


def review_audio(slug):
    review_menu = ["(p)lay", "(a)ccept", "(r)eccord again", "(t)runcate"]
    click.echo(slug)
    for i in review_menu:
        click.echo(i)
    menu_action = input(">> ")
    if menu_action == "p":
        play_audio(slug)
    elif menu_action == "a":
        exit()
    elif menu_action == "r":
        delete_audio(slug)
        find_and_record_next()
    elif menu_action == "t":
        truncate_audio(slug)
    else:
        review_audio(slug)


def record_audio(slug):
    new_recording = get_recording_file_name(slug)
    click.echo(f"Creating {new_recording}")
    click.echo("press Enter to start then CRTL-C to quit")
    wait_for_input()
    os.system(f"rec {new_recording}")


def record_silent_audio():
    silent_recording = f"{recording_directory_name}/silence.{recording_format}"
    click.echo("RECORD 5 SECONDS OF SILENCE \n" * 5)
    click.echo("press Enter to start")
    wait_for_input()
    os.system(f"rec {silent_recording} trim 0 5")
    os.system(
        f"sox {silent_recording} -n noiseprof {recording_directory_name}/noise.prof"
    )


def load_script():
    linetext = ""
    with open(script_file) as script_file_reader:
        for line in script_file_reader.readlines():

            if not line.strip():

                slug = linetext[:40].title()
                segment_name = "".join(filter(str.isalnum, slug))
                add_slug_text(segment_name, linetext)
                linetext = ""

            else:
                linetext += f"{line}  \n"


def combine_recordings_for_export():
    recording_list = []
    combined_recording = f"{recording_directory_name}/combined.{recording_format}"
    for slug, text in script_segments.items():
        recording = get_recording_file_name(slug)
        recording_list.append(recording)
    recording_list_string = " ".join(recording_list)
    print(recording_list_string)
    SOX_CMD = f"sox -V3 {recording_list_string}  {combined_recording} noisered {recording_directory_name}/noise.prof 0.21 norm -10"
    click.echo(SOX_CMD)
    os.system(SOX_CMD)


def find_and_record_next():
    for slug, text in script_segments.items():
        if recording_exists(slug):
            continue
        click.clear()
        click.echo(slug)
        click.echo("*" * 40)
        click.echo(text)
        click.echo("*" * 40)
        record_audio(slug)
        review_audio(slug)


@click.group()
def cli():
    test_sox_exists()
    pass


@cli.command()
def combine():
    "Combine Segments into single audio file"
    combine_recordings_for_export()


@cli.command()
def record():
    "Record next unrecorded segment"
    if noise_profile_missing():
        record_silent_audio()
    find_and_record_next()


@cli.command()
def silence():
    "Generate noise profile"
    record_silent_audio()



@cli.command()
def review():
    "Print segments"

    for slug, text in script_segments.items():
        click.clear()
        click.echo(slug)
        click.echo("*" * 40)
        click.echo()
        click.echo(text)
        wait_for_input()


if __name__ == "__main__":
    project_prep()
    load_script()
    cli()

Comments

Subscribe to the comments RSS feed.

Comment #1 posted on 2021-12-30 13:15:13 by Dave Morriss

Great show

A very interesting approach to recording HPR shows. Not a method that ever occurred to me - but that's what HPR is all about :-)

Great to hear your comments about MrGadgets. He was an HPR stalwart for many years, and I for one miss his contributions. I was listening to some of his shows while working on the tag project and it was great to hear him.

Comment #2 posted on 2022-01-09 16:24:54 by Reto

a good idea

Hi,
Thank you for this program and the introduction as a podcast.

I just downloaded the .zip from GitLab and while trying the commands, I realize a section with dependencies is missing. I think pip is too large, so, I usually do run it in an virtualenv.
In other Phython projects like here: https://github.com/jonaswinkler/paperless-ng/blob/master/requirements.txt you find a requirements.txt. I was wondering if you add one too?

Br,
Reto

Comment #3 posted on 2022-03-09 19:25:17 by dnt

I use it

Thanks for this! I used this for my latest episode. Still had to go to Audacity and edit it, largely to remove a ton of ums. I also then created a new script.txt in another folder, just to record a couple of bits to insert, so that it would sound the same as the rest of it. Will try to get better at writing the script and avoiding the ums so that it can go straight to HPR. Great stuff!

For listeners of the community news, since this show, norrist has put this in PyPI, so even easier to get it. Try it out!

Leave Comment

Note to Verbose Commenters
If you can't fit everything you want to say in the comment below then you really should record a response show instead.

Note to Spammers
All comments are moderated. All links are checked by humans. We strip out all html. Feel free to record a show about yourself, or your industry, or any other topic we may find interesting. We also check shows for spam :).

Provide feedback
Your Name/Handle:
Title:
Comment:
Anti Spam Question: What does the letter P in HPR stand for?
Are you a spammer?
Who is the host of this show?
What does HPR mean to you?