@ -1,20 +1,10 @@
#!/usr/bin/env bash
#!/usr/bin/env bash
# shellcheck disable=2086
# Small shell script to more easily automatically download and transcribe live stream VODs.
# This uses YT-DLP, ffmpeg and the CPP version of Whisper: https://github.com/ggerganov/whisper.cpp
# Use `./examples/yt-wsp.sh help` to print help info.
#
# Sample usage:
#
# git clone https://github.com/ggerganov/whisper.cpp
# cd whisper.cpp
# make
# ./examples/yt-wsp.sh https://www.youtube.com/watch?v=1234567890
#
# MIT License
# MIT License
# Copyright (c) 2022 Daniils Petrovs
# Copyright (c) 2022 Daniils Petrovs
# Copyright (c) 2023 Jennifer Capasso
# Permission is hereby granted, free of charge, to any person obtaining a copy
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# of this software and associated documentation files (the "Software"), to deal
@ -34,114 +24,181 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# SOFTWARE.
# Small shell script to more easily automatically download and transcribe live stream VODs.
# This uses YT-DLP, ffmpeg and the CPP version of Whisper: https://github.com/ggerganov/whisper.cpp
# Use `./examples/yt-wsp.sh help` to print help info.
#
# Sample usage:
#
# git clone https://github.com/ggerganov/whisper.cpp
# cd whisper.cpp
# make
# ./examples/yt-wsp.sh https://www.youtube.com/watch?v=1234567890
#
set -Eeuo pipefail
set -Eeuo pipefail
# You can find how to download models in the OG repo: https://github.com/ggerganov/whisper.cpp/#usage
# get script file location
MODEL_PATH = " ${ MODEL_PATH :- models /ggml-base.en.bin } " # Set to a multilingual model if you want to translate from foreign lang to en
SCRIPT_PATH = " $( realpath -e ${ BASH_SOURCE [0] } ) " ;
WHISPER_EXECUTABLE = " ${ WHISPER_EXECUTABLE :- whisper } " # Where to find the whisper.cpp executable
SCRIPT_DIR = " ${ SCRIPT_PATH %/* } "
WHISPER_LANG = " ${ WHISPER_LANG :- en } " # Set to desired lang to translate from
################################################################################
# Documentation on downloading models can be found in the whisper.cpp repo:
# https://github.com/ggerganov/whisper.cpp/#usage
#
# note: unless a multilingual model is specified, WHISPER_LANG will be ignored
# and the video will be transcribed as if the audio were in the English language
################################################################################
MODEL_PATH = " ${ MODEL_PATH :- ${ SCRIPT_DIR } /../models/ggml-base.en.bin } "
################################################################################
# Where to find the whisper.cpp executable. default to the examples directory
# which holds this script in source control
################################################################################
WHISPER_EXECUTABLE = " ${ WHISPER_EXECUTABLE :- ${ SCRIPT_DIR } /../main } " ;
# Set to desired language to be translated into english
WHISPER_LANG = " ${ WHISPER_LANG :- en } " ;
# Default to 4 threads (this was most performant on my 2020 M1 MBP)
WHISPER_THREAD_COUNT = " ${ WHISPER_THREAD_COUNT :- 4 } " ;
msg( ) {
msg( ) {
echo >& 2 -e " ${ 1 - } "
echo >& 2 -e " ${ 1 - } "
}
}
################################################################################
# create a temporary directory to work in
# set the temp_dir and temp_filename variables
################################################################################
temp_dir = " $( mktemp -d ${ SCRIPT_DIR } /tmp.XXXXXX) " ;
temp_filename = " ${ temp_dir } /yt-dlp-filename " ;
################################################################################
# for now we only take one argument
# TODO: a for loop
################################################################################
source_url = " ${ 1 } "
title_name = "" ;
cleanup( ) {
cleanup( ) {
msg "Cleaning up..."
local -r clean_me = " ${ 1 } " ;
rm -rf " ${ temp_dir } " "vod-resampled.wav" "vod-resampled.wav.srt"
if [ -d " ${ clean_me } " ] ; then
msg "Cleaning up..." ;
rm -rf " ${ clean_me } " ;
else
msg " ' ${ clean_me } ' does not appear to be a directory! " ;
exit 1;
fi ;
}
}
print_help( ) {
print_help( ) {
echo "################################################################################"
echo "Usage: ./examples/yt-wsp.sh <video_url>"
echo "Usage: ./examples/yt-wsp.sh <video_url>"
echo "See configurable env variables in the script"
echo "# See configurable env variables in the script; there are many!"
echo "This will produce an MP4 muxed file called res.mp4 in the working directory"
echo "# This script will produce an MP4 muxed file in the working directory; it will"
echo "Requirements: ffmpeg yt-dlp whisper"
echo "# be named for the title and id of the video."
echo "Whisper needs to be built into the main binary with make, then you can rename it to something like 'whisper' and add it to your PATH for convenience."
echo "# passing in https://youtu.be/VYJtb2YXae8 produces a file named" ;
echo "E.g. in the root of Whisper.cpp, run: 'make && cp ./main /usr/local/bin/whisper'"
echo "# 'Why_we_all_need_subtitles_now-VYJtb2YXae8-res.mp4'"
echo "# Requirements: ffmpeg yt-dlp whisper.cpp"
echo "################################################################################"
}
}
check_requirements( ) {
check_requirements( ) {
if ! command -v ffmpeg & >/dev/null; then
if ! command -v ffmpeg & >/dev/null; then
echo "ffmpeg is required (https://ffmpeg.org)."
echo "ffmpeg is required : https://ffmpeg.org";
exit 1
exit 1
fi
fi ;
if ! command -v yt-dlp & >/dev/null; then
if ! command -v yt-dlp & >/dev/null; then
echo "yt-dlp is required (https://github.com/yt-dlp/yt-dlp)."
echo "yt-dlp is required: https://github.com/yt-dlp/yt-dlp" ;
exit 1
exit 1;
fi
fi ;
if ! command -v " ${ WHISPER_EXECUTABLE } " & >/dev/null; then
echo "The C++ implementation of Whisper is required: https://github.com/ggerganov/whisper.cpp"
echo "Sample usage:" ;
echo "" ;
echo " git clone https://github.com/ggerganov/whisper.cpp" ;
echo " cd whisper.cpp" ;
echo " make" ;
echo " ./examples/yt-wsp.sh https://www.youtube.com/watch?v=1234567890" ;
echo "" ;
exit 1;
fi ;
if ! command -v " $WHISPER_EXECUTABLE " & >/dev/null; then
WHISPER_EXECUTABLE = "./main"
if ! command -v " $WHISPER_EXECUTABLE " & >/dev/null; then
echo "Whisper is required (https://github.com/ggerganov/whisper.cpp):"
echo "Sample usage:"
echo ""
echo " git clone https://github.com/ggerganov/whisper.cpp"
echo " cd whisper.cpp"
echo " make"
echo " ./examples/yt-wsp.sh https://www.youtube.com/watch?v=1234567890"
echo ""
exit 1
fi
fi
}
}
if [ [ $# -lt 1 ] ] ; then
if [ [ " ${# } " -lt 1 ] ] ; then
print_help
print_help;
exit 1
exit 1;
fi
fi
if [ [ " $ 1" = = "help" ] ] ; then
if [ [ " ${ 1 ##-* } " = = "help" ] ] ; then
print_help
print_help;
exit 0
exit 0;
fi
fi
temp_dir = "tmp"
check_requirements;
source_url = " $1 "
check_requirements
msg "Downloading VOD..." ;
msg "Downloading VOD..."
################################################################################
# Download the video, put the dynamic output filename into a variable.
# Optionally add --cookies-from-browser BROWSER[+KEYRING][:PROFILE][::CONTAINER] for members only VODs
# Optionally add --cookies-from-browser BROWSER[+KEYRING][:PROFILE][::CONTAINER]
# for videos only available to logged-in users.
################################################################################
yt-dlp \
yt-dlp \
-f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" \
-f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best" \
-o " ${ temp_dir } /%(title)s-%(id)s.vod.mp4 " \
--print-to-file "%(filename)s" " ${ temp_filename } " \
--no-simulate \
--no-write-auto-subs \
--restrict-filenames \
--embed-thumbnail \
--embed-thumbnail \
--embed-chapters \
--embed-chapters \
--xattrs \
--xattrs \
" ${ source_url } " -o " ${ temp_dir } /vod.mp4 "
" ${ source_url } " ;
title_name = " $( xargs basename -s .vod.mp4 < ${ temp_filename } ) " ;
msg "Extracting audio and resampling..."
msg "Extracting audio and resampling..." ;
ffmpeg -i " ${ temp_dir } / vod.mp4" \
ffmpeg -i " ${ temp_dir } / ${ title_name } . vod.mp4" \
-hide_banner \
-hide_banner \
-vn \
-loglevel error \
-loglevel error \
-ar 16000 \
-ar 16000 \
-ac 1 \
-ac 1 \
-c:a \
-c:a pcm_s16le \
pcm_s16le -y "vod-resampled.wav"
-y \
" ${ temp_dir } / ${ title_name } .vod-resampled.wav " ;
msg "Transcribing to subtitle file..."
msg "Transcribing to subtitle file..." ;
msg " Whisper specified at: ${ WHISPER_EXECUTABLE } "
msg " Whisper specified at: ' ${ WHISPER_EXECUTABLE } ' ";
$WHISPER_EXECUTABLE \
" ${ WHISPER_EXECUTABLE } " \
-m " ${ MODEL_PATH } " \
-m " ${ MODEL_PATH } " \
-l " ${ WHISPER_LANG } " \
-l " ${ WHISPER_LANG } " \
-f " vod-resampled.wav" \
-f " ${ temp_dir } / ${ title_name } . vod-resampled.wav " \
-t 8 \
-t " ${ WHISPER_THREAD_COUNT } " \
-osrt \
-osrt \
--translate
--translate;
msg "Embedding subtitle track..."
msg "Embedding subtitle track..." ;
ffmpeg -i " ${ temp_dir } / vod.mp4" \
ffmpeg -i " ${ temp_dir } / ${ title_name } . vod.mp4" \
-hide_banner \
-hide_banner \
-loglevel error \
-loglevel error \
-i " vod-resampled.wav.srt" \
-i " ${ temp_dir } / ${ title_name } . vod-resampled.wav.srt " \
-c copy \
-c copy \
-c:s mov_text \
-c:s mov_text \
-y res.mp4
-y " ${ title_name } - res.mp4" ;
cleanup
cleanup " ${ temp_dir } " ;
msg " Done! Your finished file is ready: res.mp4"
msg " Done! Your finished file is ready: ${ title_name } - res.mp4 ";