Site Map - skip to main content

Hobby Public Radio

Your ideas, projects, opinions - podcasted.

New episodes Monday through Friday.


hpr3496 :: How I record HPR Episodes

Some python to record short segments of audio.

<< First, < Previous, Latest >>

Hosted by norrist on 2021-12-27 is flagged as Clean and is released under a CC-BY-SA license.
Tags: python,sox.
Listen in ogg, spx, or mp3 format. | Comments (2)

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-30T13:15:13Z 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-09T16:24:54Z 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

<< First, < Previous, Latest >>

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 P in HPR stand for ?