I have a podcast with a friend and we were interested in uploading our episodes in full to YouTube. Unfortunately, he doesn't know how to video edit and I'm busy with my job, streaming, the podcast, and writing articles like this, so it seemed like YouTube as a podcast platform was dead to us. I did a bit of research and it turns out both our podcast platform Buzzsprout and YouTube have APIs available for uploading, which meant if I invested the time, I could bundle up the uploading work in to one script and have it done even faster.
Then the classic dilemma hit me -- do I really want to sink the time in to doing this? There's so many factors that go in to that like how much time it'll save me, how much time it'll take, will anyone else find it useful. Ultimately, I hit upon the piece of logic I often hit on when deciding what personal projects to do -- it sounds fun so why not?
Three Scripts
First off, I decided to write three separate scripts to do the following operations:
- Upload the podcast to Buzzsprout
- Create a visualization video for the podcast. Essentially a static image with audio visualization on top with the podcast as the audio
- Upload a video to YouTube
All three on their own are small, atomic operations that can be stitched together with an overarching script or CI/CD platform to form a complete workflow, but can also be used individually. Since the upload data required for both Buzzsprout and YouTube are similar (time to publish, title, description, and episode number), it makes sense to streamline the input of data in to both upload scripts and call the script just once.
Podcast Workflow Automator
Copying and pasting the code from all three scripts and making one super script does future me a disservice. By smashing them together, but also having them separate, any changes in one code base needs to also be copy/pasted in to the other code base and if I don't remember that, I'll end up fixing bugs or coding enhancements twice. As mentioned earlier, I could have one base script and call these individual scripts on their own via Bash, Powershell, or a CI/CD platform, but since Python also allows you to import code from other .py
files, I decided to keep it consistent and go for that option.
Prepping individual scripts for import
Unfortunately, the way in which I usually write standalone scripts isn't compatible with import, so a few changes had to be made. Upon initially writing the scripts, my code looked like this:
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--foo")
# script logic
if __name__ == "__main__":
try:
main()
This structure doesn't allow us to use the meat and potatoes of the script without grabbing arguments from argparse
. To fix this, we pull all the argparse
logic in to its own function, then call the main function from that. The main
function is also renamed to something more logical so when we're importing it in other functions, we're not scratching our heads wondering what main
does.
def call_with_args():
parser = argparse.ArgumentParser()
parser.add_argument("--foo")
args, _ = parser.parse_known_args()
upload_podcast(args.foo)
def upload_podcast(foo):
# upload logic
if __name__ == "__main__":
try:
call_with_args()
Now that our relevant functions have been renamed, we can move on to exposing and importing them in to our new script. First off, the base script needed to be moved to a directory with the name I wanted to use for the package. Next an __init__.py
file had to be created alongside the script that imported and exposed the primary function as part of being a module. Then a setup.py
file detailing the package and its dependencies had to be created in the base directory. All of that is to enable importing in the requirements.txt
like this:
git+https://github.com/codingvibe/audio-to-vizualization@main#egg=audio_to_visualization
git+https://github.com/codingvibe/buzzsprout-uploader@main#egg=buzzsprout_uploader
git+https://github.com/codingvibe/youtube-uploader@main#egg=youtube_uploader
Now we can import the relevant functions in to our combined script like any normal package this:
from buzzsprout_uploader import upload_podcast
from audio_to_visualization import create_vizualization
from youtube_uploader import upload_video
Was this a non-trivial amount of work to do for something nobody is likely going to use? Yes. Are there easier, dirtier ways to do this? Absolutely. Since I have the repos locally, I could have just done this:
sys.path.append('../')
from buzzsprout_uploader.buzzsprout_uploader import upload_podcast
from audio_to_visualization.audio_to_visualization import create_vizualization
from youtube_uploader.youtube_uploader import upload_video
Had I done this, I wouldn't have had to bother with the script.py
or package directory. I decided to go with the pip-importable way just to get some experience doing it and on the off chance anyone else would use this. Had I not intended to upload it to Github, I definitely would have done it the lazy way (and in fact I did on my Twitch stream and only made it importable when I wrote this article). Doing it the "right" way took quite a while and lead to this fun experience:
Final Thoughts
Is this a project more people can use than just me? Maybe, but honestly it doesn't matter -- it's going to make my life marginally easier and was fun to write. I spent probably 20 hours taking my time to upload an episode to Buzzsprout and YouTube from 20 minutes to 5, which means it'll be worth the time investment once we have 80+ episodes. As the saying goes, "why spend 10 minutes doing a task when I can spend 3 hours automating it?"
The three individual scripts you can find here:
codingvibe / audio-to-vizualization
Given an audio file and background image, make an audio visualization on the image to the audio
Audio to Visualization
The purpose of this small Python script is to transform an audio file in to a video using a background image and an audio visualizer. This tool was written leveraging ffmpeg and requires it be installed and accessible via the ffmpeg
command on the command line.
FFMPEG
Download ffmpeg
and get access to the documentation at https://www.ffmpeg.org/
Requirements
Install via pip:
pip install -r requirements.txt
Run the script
python audio_to_visualization/audio_to_visualization.py <arguments>
Command line arguments
Argument
Description
Required
Default
--audio
path to audio file to visualize
true
N/A
--background
path to image to use for background
true
N/A
--output
path and name of output file. Must end in .mp4
true
N/a
--vis-background-to-vid-ratio
ratio of visualization background height to input image height (0.0-1.0)
false
0.2
--vis-waves-to-vid-ratio
ratio of visualization waves height to input image height (0.0-1.0)
false
0.15
--vis-color
color for visualization waveforms. can be used multiple times
false
codingvibe / buzzsprout-uploader
Simple script to upload a podcast to Buzzsprout
buzzsprout-uploader
Simple script to upload a podcast to Buzzsprout.
Requirements
Install via pip:
pip install -r requirements.txt
Run the script
python buzzsprout_uploader/buzzsprout_uploader.py <arguments>
API Key and Podcast ID
A Buzzsprout API key and the ID of your podcast are both required to run this script. You can obtain both by logging in to Buzzsprout and navigating to https://www.buzzsprout.com/my/profile/api
Command line arguments
Argument
Description
Required
Default
--audio
path to audio file to upload
true
N/A
--title
title of the podcast
true
N/A
--description
description of the podcast
true
N/A
--tags
space separated list of tags for the podcast
false
[]
--publish-at-date
date to publish video (format is YYYY-MM-DD)
false
--publish-at-time
time to publish video (iso format)
false
--episode-number
episode number of this upload
false
If not supplied, most recent episode number + 1
--season-number
season number for this upload
false
If not supplied, most recent episode number
--private
whether or not
codingvibe / youtube-uploader
Updated version of the Python YouTube upload example from Google with extra command line arguments
youtube-uploader
Updated version of the Python YouTube upload example from Google with extra command line arguments.
Install Requirements
pip install
Run the script
python youtube_uploader.py <arguments>
Command line arguments
Argument
Description
Required
Default
--file
path to video file to upload
true
N/A
--title
title of the uploaded video
false
Test Title
--description
description of the video
false
Test Description
--category
YouTube category of video (https://developers.google.com/youtube/v3/docs/videoCategories/list)
false
22
--keywords
comma separated list of tags
false
--privacy-status
privacy status of video
false
public
--publish-at-date
date to publish video (format is YYYY-MM-DD)
false
--publish-at-time
time to publish video (iso format)
false
--client-secrets-file
path to client secrets json file
false
youtube-uploader-client-credentials.json
And the final workflow script is here:
codingvibe / podcast-workflow-automator
A script that uploads a podcast to Buzzsprout, creates an audio visualization of your podcast, then uploads to YouTube
podcast-workflow-automator
A script that uploads a podcast to Buzzsprout, creates an audio visualization of your podcast, then uploads to YouTube
Command line arguments
I know there are a lot. I'm sorry.
Argument | Description | Required | Default |
---|---|---|---|
--audio | path to audio file to upload | true | N/A |
--title | title of the uploaded podcast and video | true | N/A |
--description | description of the podcast and video | true | N/A |
--api-key | Buzzsprout API key to use to upload | true | N/A |
--podcast-id | ID of the podcast to upload to | true | false |
--background-image | path to image to use for background in video | true | N/A |
--tags | space separated list of tags for the podcast and video | false | |
--episode-number | episode number of this upload | false | If not supplied, most recent episode number + 1 |
--season-number | season number for this upload | false | If not supplied, most recent episode number |
--private | whether or not this upload should be private | false | false |
--publish-at-date | date |
Top comments (0)