#!/usr/bin/env bash # Copyright Ken Fallon - Released into the public domain. http://creativecommons.org/publicdomain/ #============================================================ # git clone https://github.com/davmo/fix_tags.git # git clone https://repo.anhonesthost.net/HPR/hpr-tools.git ################################################# # Variables debug="1" force_overwrite=true # Sets the behavour when files exist or not git_dir="$HOME/sourcecode/hpr/hpr_website" # The git directory for extra assets processing_dir="$HOME/tmp/hpr/processing" # The directory where the files will be copied to for processing theme="${processing_dir}/theme.flac" # The hpr theme silence="${processing_dir}/silence.flac" # A segment of silence to offset the tts in the intro outro_flac="${processing_dir}/outro.flac" # The outro edited media outro_srt="${processing_dir}/outro.srt" # The outro subtitle file intro_srt="${processing_dir}/intro.srt" # The intro subtitle template file piper_bin="/opt/bin/piper/piper/piper" # The location of the text to speech engine piper_voice="/opt/bin/piper/piper/piper-voices/en/en_US/lessac/medium/en_US-lessac-medium.onnx" working_dir_bypass="false" skip_post_show="false" skip_media_encoding="false" acceptable_duration_difference="2" # Seconds unsafe_episode_variables=( shownotes_json shownotes_html hostid artist email title summary series_id series_name explicit license ep_date ep_num tags host_license host_profile remote_media shownotes_json_sanatised ) audio_formats=( flac wav mp3 ogg opus spx ) episode_formats=( flac wav mp3 ogg opus spx srt txt ) ################################################# # Display Error message, display help and exit function echo_error() { echo -e "ERROR: $@" #1>&2 display_help_and_exit exit 1 } ################################################# # Display Information message function echo_debug() { if [ "${debug}" != "0" ] then echo -e "INFO: $@" #1>&2 fi } ################################################# # Display Help function display_help_and_exit() { echo_debug "For more information see https://repo.anhonesthost.net/HPR/hpr_documentation" exit 1 } ################################################# # Calculate difference function abs_diff() { echo $(($1 >= $2 ? $1 - $2 : $2 - $1)) } ################################################# # Program Checks function program_checks() { echo_debug "Completing program checks. program_checks()" if [ ! -d "${processing_dir}" ] then echo_error "The \"${processing_dir}\" is required but is not defined." fi if [[ ! -s "${theme}" || ! -s "${silence}" || ! -s "${outro_flac}" || ! -s "${outro_srt}" || ! -s "${intro_srt}" ]] then echo_error "The files for the theme are not available." ls -al "${theme}" "${silence}" "${outro_flac}" "${outro_srt}" "${intro_srt}" fi function is_installed() { for this_program in "$@" do if ! command -v ${this_program} 2>&1 >/dev/null then echo_error "The application \"${this_program}\" is required but is not installed." fi done } is_installed audio2image.bash awk base64 cat csvtojson curl date eval extract_images ffprobe ffmpeg file find fix_tags grep head jq kate magick mediainfo mv realpath remove-image.pl rsync seamonkey sed sed sort sponge ssh touch touch wget hpr-check-ccdn-links for arg in $* do if [ "$( echo "${arg}" | grep --count --ignore-case --perl-regexp -- '-h|--help' )" -ne "0" ] then echo_debug "Process the next SHOW_SUBMITTED show from the queue." echo_debug "If a directory is provided then the shownotes.json will be used." fi done } ################################################# # Allows override of arguments from the commandline function argument_override() { echo_debug "Overriding values with terminal arguments. argument_override()" # Argument Override if [ $# -gt 0 ] then for argument do if [[ $argument =~ ^[^=]+=.*$ ]] then this_key="${argument%=*}" this_value="${argument#*=}" for this_episode_variable in "${unsafe_episode_variables[@]}" do if [ "$( echo "${this_episode_variable}" | grep --perl-regexp --count "^${this_key}$" )" -ne "0" ] then this_value="$( echo "${this_value}" | jq --slurp --raw-input @uri | sed -e 's/%0A"$//g' -e 's/^"//g' )" echo_debug "URL encoding \"${this_key}\" as \"${this_value}\"." fi done eval "${this_key}=${this_value}" echo_debug "Replacing \"${this_key}\" with \"${this_value}\"." check_variable_is_correct ${this_key} if [ "${this_key}" == "working_dir" ] then working_dir_bypass="true" fi fi done fi } ################################################# # Variable Checks function check_variable_is_correct() { for argument in "$@" do case "${argument}" in album) if [[ -z "${album}" || "${album}" == "null" ]] then echo_error "The \"album\" variable is missing." fi if [ "$( echo "${album}" | grep --perl-regexp '^Hacker Public Radio$' | wc --lines )" -eq "0" ] then echo_error "The \"album\" variable is not \"Hacker Public Radio\"." fi ;; artist) if [[ -z "${artist}" || "${artist}" == "null" ]] then echo_error "The \"artist\" variable is missing." fi ;; assets_csv) if [[ ! -s "${assets_csv}" || -z "${assets_csv}" ]] then echo_error "The \"assets_csv\" variable/file is missing." fi if [ "$( file --brief --mime-type "${assets_csv}" | grep --count 'text/csv' )" -ne "1" ] then echo_error "The \"assets_csv\" file has not a valid \"text/csv\" mime type." fi if [ "$( wc --lines ${assets_csv} | awk '{print $1}' )" -le "1" ] then echo_error "The \"${assets_csv}\" file is empty." fi ;; assets_json) if [[ ! -s "${assets_json}" || -z "${assets_json}" ]] then echo_error "The \"assets_json\" variable/file is missing." fi if [ "$( file --brief --mime-type "${assets_json}" | grep --count 'application/json' )" -ne "1" ] then echo_error "The \"assets_json\" variable has not a valid \"application/json\" mime type." fi jq '.' "${assets_json}" >/dev/null 2>&1 if [ $? -ne 0 ] then echo_error "The file \"${assets_json}\" is not valid json." fi ;; comment) if [[ -z "${comment}" || "${comment}" == "null" ]] then echo_error "The \"comment\" variable is missing." fi ;; date) if [[ -z "${date}" || "${date}" == "null" ]] then echo_error "The \"date\" variable is missing." fi ;; duration) if [[ -z "${duration}" || "${duration}" == "null" ]] then echo_error "The \"duration\" variable is missing." fi if [[ -z "${duration}" || "${duration}" -lt "30" || "${duration}" -gt "30000" ]] then echo_error "Invalid duration missing or outside range 30 to 30000." >&2 fi ;; duration_iso8601) if [[ -z "${duration_iso8601}" || "${duration_iso8601}" == "null" ]] then echo_error "The \"duration_iso8601\" variable is missing." fi ;; email) if [ -z "${email}" ] then echo_error "The \"email\" variable is missing." fi ;; email_unpadded) if [ -z "${email_unpadded}" ] then echo_error "The \"email_unpadded\" variable is missing." fi ;; episode_body_flac) if [ -z "${episode_body_flac}" ] then echo_error "The \"episode_body_flac\" variable is missing." fi ;; episode_body_srt) if [ -z "${episode_body_srt}" ] then echo_error "The \"episode_body_srt\" variable is missing." fi ;; episode_final_flac) if [ -z "${episode_final_flac}" ] then echo_error "The \"episode_final_flac\" variable is missing." fi ;; episode_intro_flac) if [ -z "${episode_intro_flac}" ] then echo_error "The \"episode_intro_flac\" variable is missing." fi ;; episode_intro_srt) if [ -z "${episode_intro_srt}" ] then echo_error "The \"episode_intro_srt\" variable is missing." fi ;; episode_outro_srt) if [ -z "${episode_outro_srt}" ] then echo_error "The \"episode_outro_srt\" variable is missing." fi ;; episode_sandwitch_flac) if [ -z "${episode_sandwitch_flac}" ] then echo_error "The \"episode_sandwitch_flac\" variable is missing." fi ;; episode_srt) if [ -z "${episode_srt}" ] then echo_error "The \"episode_srt\" variable is missing." fi ;; episode_summary_flac) if [ -z "${episode_summary_flac}" ] then echo_error "The \"episode_summary_flac\" variable is missing." fi ;; episode_tts_flac) if [ -z "${episode_tts_flac}" ] then echo_error "The \"episode_tts_flac\" variable is missing." fi ;; episode_tts_wav) if [ -z "${episode_tts_wav}" ] then echo_error "The \"episode_tts_wav\" variable is missing." fi ;; episode_summary_json) if [[ ! -s "${episode_summary_json}" || -z "${episode_summary_json}" ]] then echo_error "The \"episode_summary_json\" variable/file is missing." fi if [ "$( file --brief --mime-type "${episode_summary_json}" | grep --count 'application/json' )" -ne "1" ] then echo_error "The \"episode_summary_json\" variable has not a valid \"application/json\" mime type." fi jq '.' "${episode_summary_json}" >/dev/null 2>&1 if [ $? -ne 0 ] then echo_error "The file \"${episode_summary_json}\" is not valid json." fi ;; ep_date) if [ -z "${ep_date}" ] then echo_error "The \"ep_date\" variable is missing." fi ;; ep_num) if [ -z "${ep_num}" ] then echo_error "The \"ep_num\" variable is missing." fi if [ "$( echo "${ep_num}" | grep --perl-regexp '^(0{0,3}[1-9]\d{0,2}|[1-9]\d{0,3})$' | wc --lines )" -eq "0" ] then echo_error "The \"ep_num\" variable is not a valid number between 1 and 9999." fi ;; expected_duration) if [ -z "${expected_duration}" ] then echo_error "The \"expected_duration\" variable is missing." fi ;; explicit) if [[ -z "${explicit}" || "${explicit}" == "null" ]] then echo_error "The \"explicit\" variable is missing." fi ;; files_json) if [[ ! -s "${files_json}" || -z "${files_json}" ]] then echo_error "The \"files_json\" variable/file is missing." fi if [ "$( file --brief --mime-type "${files_json}" | grep --count 'application/json' )" -ne "1" ] then echo_error "The \"files_json\" variable has not a valid \"application/json\" mime type." fi jq '.' "${files_json}" >/dev/null 2>&1 if [ $? -ne 0 ] then echo_error "The file \"${files_json}\" is not valid json." fi ;; files_xml) if [[ ! -s "${files_xml}" || -z "${files_xml}" ]] then echo_error "The \"files_xml\" variable/file is missing." fi if [ "$( file --brief --mime-type "${files_xml}" | grep --count 'text/xml' )" -ne "1" ] then echo_error "The \"files_xml\" variable has not a valid \"text/xml\" mime type." fi xmllint --format "${files_xml}" >/dev/null 2>&1 if [ $? -ne 0 ] then echo_error "The file \"${files_xml}\" is not valid xml." fi ;; genre) if [[ -z "${genre}" || "${genre}" == "null" ]] then echo_error "The \"genre\" variable is missing." fi ;; git_dir) if [[ -z "${git_dir}" || "${git_dir}" == "null" ]] then echo_error "The \"git_dir\" variable is missing." fi if [[ ! -d "${git_dir}" || ! -d "${git_dir}/.git" ]] then echo_error "The \"git_dir\" variable is missing or file \"${git_dir}\" does not exist." fi ;; HOME) if [[ ! -d "${HOME}" || -z "${HOME}" ]] then echo_error "The \"HOME\" variable is missing or file \"${HOME}\" does not exist." fi ;; hostid) if [[ -z "${hostid}" || "${hostid}" == "null" ]] then echo_error "The \"hostid\" variable is missing." fi ;; intro_duration) if [ -z "${intro_duration}" ] then echo_error "The \"intro_duration\" variable is missing." fi ;; intro_srt) if [[ ! -d "${intro_srt}" || -z "${intro_srt}" ]] then echo_error "The \"intro_srt\" variable is missing or file \"${intro_srt}\" does not exist." fi ;; key) if [ -z "${key}" ] then echo_error "The \"key\" variable is missing." fi ;; license) if [[ -z "${license}" || "${license}" == "null" ]] then echo_error "The \"license\" variable is missing." fi ;; license_url) if [[ -z "${license_url}" || "${license_url}" == "null" ]] then echo_error "The \"license_url\" variable is missing." fi ;; media) if [ -z "${media}" ] then echo_error "The \"media\" variable is missing." fi ;; media_basename) if [ -z "${media_basename}" ] then echo_error "The \"media_basename\" variable is missing ." fi ;; media_ffprobe) if [ -z "${media_ffprobe}" ] then echo_error "The \"media_ffprobe\" variable is missing." fi ;; media_file_mime) if [ -z "${media_file_mime}" ] then echo_error "The \"media_file_mime\" variable is missing." fi ;; media_file_mime_type) if [ -z "${media_file_mime_type}" ] then echo_error "The \"media_file_mime_type\" variable is missing." fi ;; outro_duration) if [ -z "${outro_duration}" ] then echo_error "The \"outro_duration\" variable is missing." fi ;; outro_flac) if [[ ! -s "${outro_flac}" || -z "${outro_flac}" ]] then echo_error "The \"outro_flac\" variable is missing or file \"${outro_flac}\" does not exist." fi ;; outro_srt) if [[ ! -s "${outro_srt}" || -z "${outro_srt}" ]] then echo_error "The \"outro_srt\" variable is missing or file \"${outro_srt}\" does not exist." fi ;; piper_bin) if [ -z "${piper_bin}" ] then echo_error "The \"ep_num\" variable is missing." fi ;; piper_voice) if [ -z "${ep_num}" ] then echo_error "The \"ep_num\" variable is missing." fi ;; processing_dir) if [[ ! -d "${processing_dir}" || -z "${processing_dir}" ]] then echo_error "The \"processing_dir\" variable is missing or is not a directory." fi ;; shownotes_edited) if [[ ! -s "${shownotes_edited}" || -z "${shownotes_edited}" ]] then echo_error "The \"shownotes_edited\" variable/file is missing." fi # if [ "$( file --brief --mime-type "${shownotes_edited}" | grep --count 'text/html' )" -ne "1" ] # then # echo_error "The \"shownotes_edited\" variable has not a valid \"text/html\" mime type \"${shownotes_edited}\"." # fi ;; shownotes_html) if [[ ! -s "${shownotes_html}" || -z "${shownotes_html}" ]] then echo_error "The \"shownotes_html\" variable/file is missing." fi # if [ "$( file --brief --mime-type "${shownotes_html}" | grep --count 'text/html' )" -ne "1" ] # then # echo_error "The \"shownotes_html\" variable has not a valid \"text/html\" mime type. \"${shownotes_html}\"" # fi ;; shownotes_json) if [ ! -s "${shownotes_json}" ] then echo_error "The \"shownotes_json\" variable is missing." fi if [ -z "${shownotes_json}" ] then echo_error "The \"shownotes_json\" file is missing." fi # if [ "$( file --brief --mime-type "${shownotes_json}" | grep --count 'application/json' )" -ne "1" ] # then # echo_error "The \"shownotes_json\" variable has not a valid \"application/json\" mime type." # fi jq '.' "${shownotes_json}" >/dev/null 2>&1 if [ $? -ne 0 ] then echo_error "The file \"${shownotes_json}\" is not valid json." fi ;; silence) if [[ ! -d "${silence}" || -z "${silence}" ]] then echo_error "The \"silence\" variable is missing or file \"${silence}\" does not exist." fi ;; source_duration) if [ -z "${source_duration}" ] then echo_error "The \"source_duration\" variable is missing." fi ;; summary) if [[ -z "${summary}" || "${summary}" == "null" ]] then echo_error "The \"summary\" variable is missing." fi ;; synopsis) if [[ -z "${synopsis}" || "${synopsis}" == "null" ]] then echo_error "The \"synopsis\" variable is missing." fi ;; tags) if [[ -z "${tags}" || "${tags}" == "null" ]] then echo_error "The \"tags\" variable is missing." fi ;; theme) if [[ ! -s "${theme}" || -z "${theme}" ]] then echo_error "The \"theme\" variable is missing or file \"${theme}\" does not exist." fi ;; timestamp_epoc) if [ -z "${timestamp_epoc}" ] then echo_error "The \"timestamp_epoc\" variable is missing." fi ;; title) if [[ -z "${title}" || "${title}" == "null" ]] then echo_error "The \"title\" variable is missing." fi ;; track) if [[ -z "${track}" || "${track}" == "null" ]] then echo_error "The \"track\" variable is missing." fi ;; working_dir) if [[ ! -d "${working_dir}" || -z "${working_dir}" ]] then echo_error "The \"working_dir\" variable is missing or is not a directory." fi ;; year) if [[ -z "${year}" || "${year}" == "null" ]] then echo_error "The \"year\" variable is missing." fi ;; *) echo_error "An unknown variable \"${argument}\" was provided." ;; esac done } ################################################# # Get the next show in the queue function get_next_show_from_hpr_hub() { echo_debug "Processing the next HPR Show in the queue. get_next_show_from_hpr_hub()" check_variable_is_correct processing_dir HOME if [ "$( curl --location --silent --netrc-file ${HOME}/.netrc --write-out '%{http_code}' https://hub.hackerpublicradio.org/cms/status.php --output "${processing_dir}/status.csv" )" != 200 ] then echo_error "Could not get a list of the queue status from \"https://hub.hackerpublicradio.org/cms/status.php\"" fi if [ ! -s "${processing_dir}/status.csv" ] then echo_error "Failed to retrieve \"${processing_dir}/status.csv\" from server." fi response=$( cat "${processing_dir}/status.csv" | grep ',SHOW_SUBMITTED,' | sort -t ',' -k 2 | head -1 | sed 's/,/ /g' ) if [ -z "${response}" ] then echo_debug "Getting a list of all the reservations." curl --location --silent --netrc-file ${HOME}/.netrc "https://hub.hackerpublicradio.org/cms/status.php" | sort -n echo_error "There appear to be no more shows with the status \"SHOW_SUBMITTED\"." fi timestamp_epoc="$( echo ${response} | awk '{print $1}' )" ep_num="$( echo ${response} | awk '{print $2}' )" ep_date="$( echo ${response} | awk '{print $3}' )" key="$( echo ${response} | awk '{print $4}' )" email="$( echo ${response} | awk '{print $6}' )" email_unpadded="$( echo $email | sed 's/.nospam@nospam./@/g' )" hpr_upload_dir="hub.hackerpublicradio.org/upload/${timestamp_epoc}_${ep_num}_${ep_date}_${key}" source_dir="hpr:${hpr_upload_dir}" dest_dir="${timestamp_epoc}_${ep_num}_${ep_date}_${key}" working_dir="${processing_dir}/${dest_dir}" check_variable_is_correct timestamp_epoc ep_num ep_date key email email_unpadded echo_debug "Downloading hpr${ep_num} from ${email_unpadded}" echo_debug "" echo_debug "rsync -ave ssh --partial --progress ${source_dir}/ ${working_dir}/" rsync -ave ssh --partial --progress ${source_dir}/ ${working_dir}/ } ################################################# # Get the show information from a local directory function get_ep_num_from_local_dir() { echo_debug "Processing a local directory. get_ep_num_from_local_dir()" check_variable_is_correct working_dir if [ ! -s "${shownotes_json}" ] then echo_debug "Could not find a \"shownotes.json\" in the working directory \"${working_dir}/\"" if [ -z "${ep_num}" ] then echo_debug "Attempting to get episode number from the \"${working_dir}\"" ep_num="$( echo "${working_dir}" | grep --color=never --perl-regexp --only-matching '_[0-9]{4}_' | sed 's/_//g' )" fi if [ -z "${ep_num}" ] then echo_error "Could not find the episode number - please rerun with \"$0 ep_num=9876\"" fi echo_debug "Attempting to download information for episode \"${ep_num}\"" if [ "$( curl --location --silent --netrc-file ${HOME}/.netrc --write-out '%{http_code}' https://hub.hackerpublicradio.org/cms/shownotes.php?id=${ep_num} --output "${shownotes_json}" )" != 200 ] then echo_error "The Episode hpr${ep_num} has not been posted." fi if [ ! -s "${shownotes_json}" ] then echo_error "The Episode information for hpr${ep_num} failed to download." fi fi check_variable_is_correct shownotes_json } ################################################# # Get the show either the next in the queue # or from a local queue directory function get_working_dir() { echo_debug "Getting working directory and populating show information. get_working_dir()" if [ $# -eq 0 ] then get_next_show_from_hpr_hub fi check_variable_is_correct working_dir if [ -z "${ep_num}" ] then get_ep_num_from_local_dir $@ check_variable_is_correct ep_num fi echo_debug "Found working directory as \"${working_dir}\"" } ################################################# # Once the working_dir is known, set the other variables function set_working_dir_variables() { echo_debug "Setting the other working directory and variables. set_working_dir_variables()" check_variable_is_correct working_dir shownotes_json="${working_dir}/shownotes.json" shownotes_html="${working_dir}/shownotes.html" shownotes_edited="${working_dir}/shownotes_edited.html" episode_tts_wav="${working_dir}/episode_tts.wav" episode_tts_flac="${working_dir}/episode_tts.flac" episode_summary_flac="${working_dir}/episode_summary.flac" episode_intro_flac="${working_dir}/episode_intro.flac" episode_body_flac="${working_dir}/episode_body.flac" episode_sandwitch_flac="${working_dir}/episode_sandwitch.flac" episode_final_flac="${working_dir}/episode_final.flac" episode_intro_srt="${working_dir}/episode_intro.srt" episode_body_srt="${working_dir}/episode_body.srt" episode_outro_srt="${working_dir}/episode_outro.srt" episode_srt="${working_dir}/episode.srt" } ################################################# # Provides all the metadata we need to process the show. function get_episode_metadata() { echo_debug "Extracting the episode metadata. get_episode_metadata()" check_variable_is_correct working_dir set_working_dir_variables # check_variable_is_correct shownotes_html shownotes_edited if [[ -n "${skip_post_show}" && "${skip_post_show}" == "true" ]] then echo_debug "The Episode hpr${ep_num} has already been posted. Skipping get_episode_metadata()" return fi check_variable_is_correct shownotes_json hostid="$( jq --raw-output '.host.Host_ID' ${shownotes_json} )" artist="$( jq --raw-output '.host.Host_Name' ${shownotes_json} )" email="$( jq --raw-output '.host.Host_Email' ${shownotes_json} )" email_padded="$( echo $email | sed 's/@/.nospam@nospam./g' )" title="$( jq --raw-output '.episode.Title' ${shownotes_json} )" summary="$( jq --raw-output '.episode.Summary' ${shownotes_json} )" series_id="$( jq --raw-output '.episode.Series' ${shownotes_json} )" series_name="$( jq --raw-output '.episode.Series_Name' ${shownotes_json} )" explicit="$( jq --raw-output '.episode.Explicit' ${shownotes_json} )" license="$( jq --raw-output '.episode.Show_License' ${shownotes_json} )" ep_date="$( jq --raw-output '.metadata.Episode_Date' ${shownotes_json} )" ep_num="$( jq --raw-output '.metadata.Episode_Number' ${shownotes_json} )" key="$( jq --raw-output '.metadata.Key' ${shownotes_json} )" tags="$( jq --raw-output '.episode.Tags' ${shownotes_json} )" host_license="$( jq --raw-output '.host.Host_License' ${shownotes_json} )" host_profile="$( jq --raw-output '.host.Host_Profile' ${shownotes_json} )" remote_media="$( jq --raw-output '.metadata.url' ${shownotes_json} )" shownotes_json_sanatised=$( jq 'del(.episode.Show_Notes, .metadata.Host_IP)' "${shownotes_json}" ) echo_debug "Extracting shownotes html from json file." jq --raw-output '.episode.Show_Notes' "${shownotes_json}" > "${shownotes_html}" check_variable_is_correct shownotes_html ( echo '
' cat "${shownotes_html}" echo ' ' ) | sponge "${shownotes_html}" unsafe_episode_variables=( shownotes_json shownotes_html hostid artist email title summary series_id series_name explicit license ep_date ep_num tags host_license host_profile remote_media shownotes_json_sanatised ) for episode_variable in "${unsafe_episode_variables[@]}" do if [[ -z ${!episode_variable} && "${episode_variable}" != "remote_media" ]] then # indirect expansion here echo_error "The episode_variable \"${episode_variable}\" is missing."; else echo_debug "The episode_variable \"${episode_variable}\" is set to \"${!episode_variable}\""; fi done argument_override "$@" # Hosts need to exist in the database if [ "${hostid}" == '0' ] then echo_error "The hostid is 0. Create the host and use \"hostid=???\" to override" fi } ################################################# # Extract_images by brute force function extract_images_brute_force() { echo_debug "Extracting images with grep. extract_images_brute_force()" if [[ -n "${skip_post_show}" && "${skip_post_show}" == "true" ]] then echo_debug "The Episode hpr${ep_num} has already been posted. Skipping extract_images_brute_force()" return fi if [ -s "${shownotes_edited}" ] then if [ "$( grep -c '' "${shownotes_edited}" )" -eq "0" ] then echo_debug "There is already an edited version of the shownotes at \"${shownotes_edited}\", slipping image extraction." return fi echo_debug "There is an unfinished edited version of the shownotes at \"${shownotes_edited}\", extracting images." fi if [[ -z "${shownotes_html}" || ! -s "${shownotes_html}" ]] then echo_error "The shownotes_html file \"${shownotes_html}\" could not be found." fi ## TODO fix for https://repo.anhonesthost.net/HPR/hpr_hub/issues/102 and https://github.com/slab/quill/discussions/4766 sed -e 's/]*>//g; s/<\/span>//g' -e 's/]*>//g; s/<\/code>/<\/code>/g' -e "s#>#>\n#g" "${shownotes_html}" | sponge "${shownotes_html}"
## TODO Temp fix until https://repo.anhonesthost.net/HPR/hpr-tools/issues/3 is available
# Extract embedded images
image_count_embedded="1"
for image in $( grep --color=never --perl-regexp --only-matching 'data:image/[^;]*;base64,\K[a-zA-Z0-9+/=]*' "${shownotes_html}" )
do
this_image="${working_dir}/hpr${ep_num}_image_${image_count_embedded}"
echo -n "$image" | base64 -di > ${this_image}
this_ext="$( file --mime-type ${this_image} | awk -F '/' '{print $NF}' )"
mv -v "${this_image}" "${this_image}.${this_ext}"
this_width="$( mediainfo "${this_image}.${this_ext}" | grep Width | awk -F ': | pixels' '{print $2}' | sed 's/ //g' )"
if [ "${this_width}" -gt "400" ]
then
echo_debug "Generating thumbnail for embedded image \"${this_image}.${this_ext}\"."
magick "${this_image}.${this_ext}" -resize 400x "${this_image}_tn.${this_ext}"
fi
((image_count_embedded=image_count_embedded+1))
done
# Download referenced images
image_count_external="1"
for image in $( grep --color=never --perl-regexp --only-matching '' "${shownotes_html}" | awk -F 'src=' '{print $2}' | awk -F '"' '{print $2}' )
do
this_image="${working_dir}/hpr${ep_num}_image_ext_${image_count_external}"
wget "${image}" --output-document=${this_image}
if [ -s "${this_image}" ]
then
this_ext="$( file --mime-type ${this_image} | awk -F '/' '{print $NF}' )"
mv -v "${this_image%.*}" "${this_image}.${this_ext}"
this_width="$( mediainfo "${this_image}.${this_ext}" | grep Width | awk -F ': | pixels' '{print $2}' | sed 's/ //g' )"
if [ "${this_width}" -gt "400" ]
then
echo_debug "Generating thumbnail for external image \"${this_image}.${this_ext}\"."
magick "${this_image}.${this_ext}" -resize 400x "${this_image}_tn.${this_ext}"
fi
((image_count_external=image_count_external+1))
else
echo_debug "Could not download external image \"${image}\"."
fi
done
cat "${shownotes_html}" | remove-image.pl | sponge "${shownotes_html}"
if [ "${image_count_embedded}" -gt "1" ]
then
image_count_embedded="1"
touch "${shownotes_html}.embedded_images"
cat "${shownotes_html}" | while read this_line
do
if [ "$( echo "${this_line}" | grep --count "LOCAL_IMAGE_REMOVED" )" -eq "0" ]
then
echo "${this_line}" >> "${shownotes_html}.embedded_images"
else
this_image="$( find "${working_dir}/" -type f -iname "hpr${ep_num}_image_${image_count_embedded}.*" )"
if [[ -z "${this_image}" || ! -s "${this_image}" ]]
then
echo_error "Unable to find an image for \"${image_count_embedded}\", \"${this_image}\"."
fi
this_image="$( basename "${this_image}" )"
this_image_tn="$( find "${working_dir}/" -type f -iname "${this_image%.*}_tn.*" )"
if [[ -z "${this_image_tn}" || ! -s "${this_image_tn}" ]]
then
echo "${this_line}" | sed "s@LOCAL_IMAGE_REMOVED@${this_image}@g" >> "${shownotes_html}.embedded_images"
else
this_image_tn="$( basename "${this_image_tn}" )"
echo "" >> "${shownotes_html}.embedded_images"
echo "${this_line}" | sed "s@LOCAL_IMAGE_REMOVED@${this_image_tn}@g" >> "${shownotes_html}.embedded_images"
echo "" >> "${shownotes_html}.embedded_images"
fi
((image_count_embedded=image_count_embedded+1))
fi
done
mv -v "${shownotes_html}.embedded_images" "${shownotes_html}"
else
echo_debug "No embedded images found. ${image_count_embedded}"
fi
if [ "${image_count_external}" -gt "1" ]
then
image_count_external="1"
touch "${shownotes_html}.external_images"
cat "${shownotes_html}" | remove-image.pl | while read this_line
do
if [ "$( echo "${this_line}" | grep --count "REMOTE_IMAGE_REMOVED" )" -eq "0" ]
then
echo "${this_line}" >> "${shownotes_html}.external_images"
else
this_image="$( find "${working_dir}/" -type f -iname "hpr${ep_num}_image_ext_${image_count_external}.*" )"
if [[ -z "${this_image}" || ! -s "${this_image}" ]]
then
echo_error "Unable to find an image for \"${image_count_external}\", \"${this_image}\"."
fi
this_image="$( basename "${this_image}" )"
this_image_tn="$( find "${working_dir}/" -type f -iname "${this_image%.*}_tn.*" )"
if [[ -z "${this_image_tn}" || ! -s "${this_image_tn}" ]]
then
echo "${this_line}" | sed "s@REMOTE_IMAGE_REMOVED@${this_image}@g" >> "${shownotes_html}.external_images"
else
this_image_tn="$( basename "${this_image_tn}" )"
echo "" >> "${shownotes_html}.external_images"
echo "${this_line}" | sed "s@REMOTE_IMAGE_REMOVED@${this_image_tn}@g" >> "${shownotes_html}.external_images"
echo "" >> "${shownotes_html}.external_images"
fi
((image_count_external=image_count_external+1))
fi
done
mv -v "${shownotes_html}.external_images" "${shownotes_html}"
else
echo_debug "No external images found."
fi
## TODO End Temp fix
}
#################################################
## Media Checks
function media_checks() {
echo_debug "Running media checks. media_checks()"
check_variable_is_correct working_dir
if [[ -n "${remote_media}" && "${remote_media}" != "null" ]]
then
echo_debug "Fetching remote media from \"${remote_media}\""
wget --timestamping --directory-prefix="${working_dir}/" "${remote_media}"
if [ $? -ne 0 ]
then
echo_error "Could not get the remote media"
fi
fi
media=$( find "${working_dir}/" -maxdepth 1 -type f -exec file --mime-type {} \; | grep -Ei ' audio/| video/' | awk -F ': ' '{print $1}' )
if [ -z "${media}" ]
then
find "${working_dir}/" -type f
echo_error "Can't find any media in \"${working_dir}/\""
fi
media_basename="$( basename "${media}" )"
if [ -z "${media_basename}" ]
then
echo_error "Could not create the media_basename \"${media_basename}/\""
fi
if [ "$( echo "${media}" | wc --lines )" -ne 1 ]
then
echo "Multiple files found. Which one do you want to use ?"
select this_media in "Skip asset creation" $( echo "${media}" )
do
if [ "${this_media}" == "Skip asset creation" ]
then
echo_debug "Skip asset creation"
check_derived_assets
skip_media_encoding="true"
return
fi
ls -al "${this_media}"
media="${this_media}"
break
done
fi
echo_debug "You selected \"${media}\"."
if [[ -z "${media}" || ! -s "${media}" ]]
then
echo_error "Could not find the media \"${media}/\""
fi
shownotes_srt="${media%.*}.srt"
if [[ -z "${shownotes_srt}" || ! -s "${shownotes_srt}" ]]
then
echo_error "Could not find the subtitles for media \"${media}\" in \"${shownotes_srt}\""
fi
#TODO fix close duration
# Find duration
duration=$( mediainfo --full --Output=JSON "${media}" | jq --raw-output '.media.track | .[] | select(."@type"=="Audio") | .Duration' | awk -F '.' '{print $1}' )
if [[ -z "${duration}" || "${duration}" -lt "30" || "${duration}" -gt "30000" ]]
then
echo_error "Invalid duration found in \"${media}\"" >&2
fi
echo_debug "The Duration is \"${duration}\" seconds from \"${media}\""
#TODO fix close duration
# Find number of channels ( 1=mono or 2=stereo)
supplied_channels=$( mediainfo --full --Output=JSON "${media}" | jq --raw-output '.media.track | .[] | select(."@type"=="Audio") | .Channels' )
if [[ -z "${supplied_channels}" || "${supplied_channels}" -lt "1" || "${supplied_channels}" -gt "2" ]]
then
echo_error "Invalid number of audio channels \"${supplied_channels}\" found in \"${media}\"" >&2
fi
echo_debug "The number of audio channels is \"${supplied_channels}\" from \"${media}\" ."
# Gernerate the Spectrum and Waveform image
ffmpeg -hide_banner -loglevel error -y -i "${media}" -lavfi "showspectrumpic=s=960x540" "${working_dir}/${media_basename%.*}_spectrum.png"
audio2image.bash "${media}" && mv -v "${media%.*}.png" "${working_dir}/${media_basename%.*}_waveform.png"
# Getting metadata
mediainfo "${media}" > "${working_dir}/${media_basename%.*}_mediainfo.txt"
exiftool "${media}" > "${working_dir}/${media_basename%.*}_exiftool.txt"
for check_file in spectrum.png waveform.png mediainfo.txt exiftool.txt
do
if [ ! -s "${working_dir}/${media_basename%.*}_${check_file}" ]
then
echo_error "The ${check_file} file was not generated for the \"${working_dir}/${media_basename%.*}_${check_file}\"" >&2
fi
done
media_ffprobe="$( ffprobe ${media} 2>&1 | grep Audio: | sed 's/^.\s//g' )"
media_file_mime="$( file --brief --mime ${media} )"
media_file_mime_type="$( file --brief --mime-type ${media} )"
check_variable_is_correct media_ffprobe media_file_mime media_file_mime_type
}
#################################################
## Generate Initial Report for review by the Janitors
function generate_initial_report() {
echo_debug "Generating the initial report. generate_initial_report()"
# if [[ -n "${skip_post_show}" && "${skip_post_show}" == "true" ]]
# then
# echo_debug "The Episode hpr${ep_num} has already been posted. Skipping generate_initial_report()"
# return
# fi
# TODO list the images.
echo "
Hacker Public Radio ~ The Technology Community Podcast
Skip to Derived Media
Field Mapping
Field Value
hostid ${hostid}
host_name ${artist}
title ${title}
summary ${summary}
series_id ${series_id}
series_name ${series_name}
explicit ${explicit}
episode_license ${license}
tags ${tags}
host_license ${host_license}
host_profile ${host_profile}
Raw shownotes.json
${shownotes_json_sanatised}
Audio
mediainfo report
$( cat "${working_dir}/${media_basename%.*}_mediainfo.txt" )
exiftool report
$( cat "${working_dir}/${media_basename%.*}_exiftool.txt" )
Audio Spectrum
Audio Waveform
${media_ffprobe}
${media_file_mime}
Transcript
$(cat "${shownotes_srt}" )
" > "${working_dir}/${media_basename%.*}_media_report.html"
}
#################################################
## Manually edit the shownotes to fix issues
function manual_shownotes_review() {
echo_debug "Validating the initial report. manual_shownotes_review()"
if [[ -n "${skip_post_show}" && "${skip_post_show}" == "true" ]]
then
echo_debug "The Episode hpr${ep_num} has already been posted. Skipping manual_shownotes_review()"
return
fi
if [[ -z "${shownotes_html}" || ! -s "${shownotes_html}" || ! -s "${working_dir}/${media_basename%.*}_media_report.html" ]]
then
echo "shownotes_html: ${shownotes_html}"
ls -al "${shownotes_html}" "${working_dir}/${media_basename%.*}_media_report.html"
echo_error "The files needed for to generate the inital report information are not available."
fi
if [ -s "${shownotes_edited}" ]
then
if [ "$( grep -c '' "${shownotes_edited}" )" -eq "0" ]
then
echo_debug "There is already an edited version of the shownotes at \"${shownotes_edited}\", skipping manual_shownotes_review."
return
fi
echo_debug "There is an unfinished edited version of the shownotes at \"${shownotes_edited}\", leaving it intact."
else
echo_debug "Extracting the shownotes to \"${shownotes_edited}\"."
cp -v "${shownotes_html}" "${shownotes_edited}"
fi
if [ ! -s "${shownotes_edited}" ]
then
echo_error "The edited shownotes are missing \"${shownotes_edited}\"."
fi
kate "${shownotes_edited}" >/dev/null 2>&1 &
librewolf "${working_dir}/${media_basename%.*}_media_report.html" >/dev/null 2>&1 &
seamonkey "${shownotes_edited}" >/dev/null 2>&1 &
# # # # bluefish "${shownotes_edited}" >/dev/null 2>&1 &
# https://markdowntohtml.com/
read -p "Does the metadata 'look ok ? (N|y) ? " -n 1 -r
echo # (optional) move to a new line
if [[ ! $REPLY =~ ^[yY]$ ]]
then
echo_error "The final review was not approved."
fi
# remove extra wrappers that seamonkey adds
grep --invert-match --perl-regexp '|head>|' "${shownotes_edited}" | sponge "${shownotes_edited}"
# Check to see if images have been linked TODO make a loop for found images
if [ "$( find "${working_dir}" -type f -iname "*_image_*" | wc --lines )" -ne "0" ]
then
if [ "$( grep --count "_image_" "${shownotes_edited}" )" -eq "0" ]
then
echo_error "The extracted images were not linked in the shownotes \"${shownotes_edited}\"."
fi
fi
}
#################################################
# Post show to HPR
function post_show_to_hpr_db() {
echo_debug "Posting the show to the HPR DB. post_show_to_hpr_db()"
if [[ -n "${skip_post_show}" && "${skip_post_show}" == "true" ]]
then
echo_debug "The Episode hpr${ep_num} has already been posted. Skipping post_show_to_hpr_db()"
return
fi
if [ ! -s "${shownotes_edited}" ]
then
echo_error "Failed to find the extracted shownote html file \"${shownotes_edited}\""
fi
notes="$( cat "${shownotes_edited}" | jq --slurp --raw-input @uri | sed -e 's/%0A"$//g' -e 's/^"//g' )"
host_profile_encoded="$( echo "${host_profile}" | jq --slurp --raw-input @uri | sed -e 's/%0A"$//g' -e 's/^"//g' )"
title_encoded="$( echo "${title}" | jq --slurp --raw-input @uri | sed -e 's/%0A"$//g' -e 's/^"//g' )"
summary_encoded="$( echo "${summary}" | jq --slurp --raw-input @uri | sed -e 's/%0A"$//g' -e 's/^"//g' )"
post_show_json="${working_dir}/post_show.json"
echo "Sending:"
echo " key=${key}
ep_num=${ep_num}
ep_date=${ep_date}
email=${email_padded}
title=${title_encoded}
duration=${duration}
summary=${summary_encoded}
series_id=${series_id}
series_name=${series_name}
explicit=${explicit}
episode_license=${license}
tags=${tags}
hostid=${hostid}
host_name=${artist}
host_license=${host_license}
host_profile=${host_profile_encoded}
notes=REMOVED"
echo "{
\"key\": \"${key}\",
\"ep_num\": \"${ep_num}\",
\"ep_date\": \"${ep_date}\",
\"email\": \"${email_padded}\",
\"title\": \"${title_encoded}\",
\"duration\": \"${duration}\",
\"summary\": \"${summary_encoded}\",
\"series_id\": \"${series_id}\",
\"series_name\": \"${series_name}\",
\"explicit\": \"${explicit}\",
\"episode_license\": \"${license}\",
\"tags\": \"${tags}\",
\"hostid\": \"${hostid}\",
\"host_name\": \"${artist}\",
\"host_license\": \"${host_license}\",
\"host_profile\": \"${host_profile_encoded}\",
\"notes\": \"${notes}\"
}" > "${post_show_json}"
jq '.' "${post_show_json}"
if [ $? -ne 0 ]
then
echo_error "The file \"${post_show_json}\" is not valid json."
fi
curl --netrc --show-headers --request POST "https://hub.hackerpublicradio.org/cms/add_show_json.php" --header "Content-Type: application/json" --data-binary "@${post_show_json}"
if [ "$( curl --location --silent --netrc --write-out '%{http_code}' https://hub.hackerpublicradio.org/cms/say.php?id=${ep_num} --output /dev/null )" != 200 ]
then
echo_error "The Episode hpr${ep_num} has not been posted"
fi
}
#################################################
# Get the
function get_variables_from_episode_summary_json() {
echo_debug "Creating Text to Speech summary. get_variables_from_episode_summary_json()"
check_variable_is_correct ep_num working_dir
set_working_dir_variables
if [ -z "${episode_summary_json}" ]
then
episode_summary_json="${working_dir}/episode_summary.json"
fi
if [ ! -s "${episode_summary_json}" ]
then
echo_debug "The \"episode_summary_json\" variable/file is missing, getting a new version."
if [ "$( curl --location --silent --netrc --write-out '%{http_code}' https://hub.hackerpublicradio.org/cms/say.php?id=${ep_num} --output "${episode_summary_json}" )" != 200 ]
then
echo_error "The Episode hpr${ep_num} has not been posted"
fi
fi
check_variable_is_correct episode_summary_json
for episode_summary_key in $( jq --raw-output '. | keys | @tsv' "${episode_summary_json}" )
do
episode_summary_value="$( jq --raw-output ".${episode_summary_key}" "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
export "${episode_summary_key}=${episode_summary_value}"
echo_debug "Setting \"${episode_summary_key}\" to \"${episode_summary_value}\" from \"$( basename ${episode_summary_json} )\""
check_variable_is_correct ${episode_summary_key}
done
argument_override "$@"
duration_iso8601="$( \date -d@${duration} -u +%H:%M:%S )"
check_variable_is_correct duration_iso8601
}
#################################################
# Generate text to speech summary
function create_tts_summary() {
echo_debug "Creating Text to Speech summary. create_tts_summary()"
if [[ -n "${skip_media_encoding}" && "${skip_media_encoding}" == "true" ]]
then
echo_debug "Skipping media creation from create_tts_summary()"
return
fi
check_variable_is_correct working_dir duration synopsis piper_bin piper_voice
echo_debug "Converting text synopsis \"${synopsis}\" to speech."
echo "${synopsis}" | "${piper_bin}" --model "${piper_voice}" --output_file "${episode_tts_wav}"
check_variable_is_correct episode_tts_wav
}
#################################################
# Generate Intro
function generate_intro() {
echo_debug "Generating the intro. generate_intro()"
if [[ -n "${skip_media_encoding}" && "${skip_media_encoding}" == "true" ]]
then
echo_debug "Skipping media creation from generate_intro()"
return
fi
check_variable_is_correct working_dir theme media outro_flac
# Everything needs to be in the same format for the intro, 1 channel (mono) Sampling rate 44.1 kHz
ffmpeg -hide_banner -loglevel error -y -i "${episode_tts_wav}" -ar 44100 -ac 1 "${episode_tts_flac}"
check_variable_is_correct episode_tts_flac
# A level of silence is added at the beginning of the text to speech
sox -V2 "${silence}" "${episode_tts_flac}" "${episode_summary_flac}"
check_variable_is_correct episode_summary_flac
# The tracks are merged together resulting in the theme playing first, then after a period of silence the text to speech enters
sox -V2 -m "${episode_summary_flac}" "${theme}" ${episode_intro_flac}
check_variable_is_correct episode_intro_flac
}
#################################################
# Generate parent audio - the sandwitch
function generate_parent_audio() {
echo_debug "Generating the parent audio - the sandwitch. generate_parent_audio()"
if [[ -n "${skip_media_encoding}" && "${skip_media_encoding}" == "true" ]]
then
echo_debug "Skipping media creation from generate_parent_audio()"
return
fi
check_variable_is_correct episode_intro_flac media outro_flac
# Everything needs to be in the same format so the text to speech needs to be converted to 2 channel Sampling rate 44.1 kHz
ffmpeg -hide_banner -loglevel error -y -i "${media}" -ar 44100 -ac 1 "${episode_body_flac}"
check_variable_is_correct episode_body_flac
# Combine the components together
sox -V2 ${episode_intro_flac} "${episode_body_flac}" "${outro_flac}" "${episode_sandwitch_flac}"
check_variable_is_correct episode_sandwitch_flac
# Normalise the audio
ffmpeg -hide_banner -loglevel error -y -i "${episode_sandwitch_flac}" -af loudnorm=I=-16:LRA=11:TP=-1.5 "${episode_final_flac}"
check_variable_is_correct episode_final_flac
}
#################################################
# Generate derived media
function generate_derived_media() {
echo_debug "Generating derived audio. generate_derived_media()"
if [[ -n "${skip_media_encoding}" && "${skip_media_encoding}" == "true" ]]
then
echo_debug "Skipping media creation from generate_derived_media()"
return
fi
check_variable_is_correct media working_dir ep_num title artist license
if [[ ! -s "${episode_final_flac}" ]]
then
ls -al
echo_error "The final cut is not available."
fi
check_variable_is_correct comment year
# https://wiki.multimedia.cx/index.php?title=FFmpeg_Metadata
for extension in "${audio_formats[@]}"
do
echo_debug "Generating \"hpr${ep_num}.${extension}\"."
ffmpeg -hide_banner -loglevel error -y -i "${episode_final_flac}" \
-metadata title="${title}" \
-metadata artist="${artist}" \
-metadata author="${artist}" \
-metadata album="Hacker Public Radio" \
-metadata comment="${comment} The license is ${license}" \
-metadata year="${year}" \
-metadata track="${ep_num}" \
-metadata genre="Podcast" \
-metadata language="English" \
-metadata copyright="${license}" \
"${working_dir}/hpr${ep_num}.${extension}"
fix_tags -album="Hacker Public Radio" -artist="${artist}" -comment="${comment} The license is ${license}" -genre="Podcast" -title="${title}" -track="${ep_num}" -year="${year}" "${working_dir}/hpr${ep_num}.${extension}"
if [[ ! -s "${working_dir}/hpr${ep_num}.${extension}" ]]
then
echo_error "Failed to generate \"${working_dir}/hpr${ep_num}.${extension}\"."
ls -al "${working_dir}/hpr${ep_num}.${extension}"
fi
done
#TODO fix close duration
# shownotes_json="${working_dir}/shownotes.json"
# shownotes_html="${working_dir}/shownotes.html"
# shownotes_edited="${working_dir}/shownotes_edited.html"
# episode_tts_wav="${working_dir}/episode_tts.wav"
# episode_tts_flac="${working_dir}/episode_tts.flac"
# episode_summary_flac="${working_dir}/episode_summary.flac"
# episode_intro_flac="${working_dir}/episode_intro.flac"
# episode_body_flac="${working_dir}/episode_body.flac"
# episode_sandwitch_flac="${working_dir}/episode_sandwitch.flac"
# episode_final_flac="${working_dir}/episode_final.flac"
# episode_intro_srt="${working_dir}/episode_intro.srt"
# episode_body_srt="${working_dir}/episode_body.srt"
# episode_outro_srt="${working_dir}/episode_outro.srt"
# episode_srt="${working_dir}/episode.srt"
check_variable_is_correct episode_intro_flac outro_flac media duration
intro_duration="$( mediainfo --Output=XML --Full "${episode_intro_flac}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
outro_duration="$( mediainfo --Output=XML --Full "${outro_flac}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
source_duration="$( mediainfo --Output=XML --Full "${media}" | xmlstarlet sel -T -t -m '/_:MediaInfo/_:media/_:track[@type="Audio"]' -v '_:Duration' -n | awk -F '.' '{print $1}' )"
expected_duration=$(( ${intro_duration} + ${duration} + ${outro_duration} ))
check_variable_is_correct intro_duration outro_duration source_duration expected_duration
echo_debug "Intro(${intro_duration}) + Show(${duration}) + Outro(${outro_duration}) = ${expected_duration}"
for extension in "${audio_formats[@]}"
do
this_duration=$( mediainfo --full --Output=XML "${working_dir}/hpr${ep_num}.${extension}" | xmlstarlet sel -T -t -m "_:MediaInfo/_:media/_:track[@type='Audio']/_:Duration[1]" -v "." -n - | awk -F '.' '{print $1}' )
if [ $( abs_diff "${this_duration}" "${expected_duration}" ) -le "${acceptable_duration_difference}" ]
then
echo_debug "The file \"hpr${ep_num}.${extension}\" duration of ${this_duration} is close enough to ${expected_duration}"
else
echo_error "The file \"hpr${ep_num}.${extension}\" actual duration of ${this_duration} is not close enough to posted duration of ${expected_duration}. Fix or update the posted duration to ${source_duration}. Try using \"sed -i 's/\"${duration}\"/\"${source_duration}\"/g' ${episode_summary_json}\""
fi
done
cp -v "${media}" "${working_dir}/hpr${ep_num}_source.${media##*.}"
if [[ ! -s "${working_dir}/hpr${ep_num}_source.${media##*.}" ]]
then
ls -al "${working_dir}/hpr${ep_num}_source.${media##*.}"
echo_error "Failed to copy \"${working_dir}/hpr${ep_num}_source.${media##*.}\"."
fi
}
#################################################
# Generate Subtitles
function generate_show_transcript() {
echo_debug "Generate show transcript and subtitles. generate_show_transcript()"
if [[ -n "${skip_media_encoding}" && "${skip_media_encoding}" == "true" ]]
then
echo_debug "Skipping media creation from generate_show_transcript()"
return
fi
# TODO Currently processed elsewhere by hpr-get-and-transcode.bash and uploaded to hpr:upload/ to be synced with media above
if [[ ! -s "${media}" || ! -s "${media%.*}.srt" || ! -s "${intro_srt}" || ! -s "${outro_srt}" || ! -s ${episode_intro_flac} || ! -s "${episode_body_flac}" ]]
then
ls -al "${media}" "${media%.*}.srt" "${intro_srt}" "${outro_srt}" ${episode_intro_flac} "${episode_body_flac}"
echo_error "The transcriptions files are not available."
fi
# Copy in the intro subtitle template and replace each line with the text with the summary
date="$( jq --raw-output '.date' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
title="$( jq --raw-output '.title' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
duration="$( jq --raw-output '.duration' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
duration_iso8601="$( \date -d@${duration} -u +%H:%M:%S )"
artist="$( jq --raw-output '.artist' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
explicit="$( jq --raw-output '.explicit' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
license="$( jq --raw-output '.license' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
summary="$( jq --raw-output '.summary' "${episode_summary_json}" | sed -e 's/ \././g' -e 's/\.\./\./g' -e 's/ / /g' )"
if [[ -z "${date}" || "${date}" == "null" || -z "${title}" || "${title}" == "null" || -z "${duration_iso8601}" || "${duration_iso8601}" == "null" || -z "${artist}" || "${artist}" == "null" || -z "${explicit}" || "${explicit}" == "null" || -z "${license}" || "${license}" == "null" || -z "${summary}" || "${summary}" == "null" ]]
then
echo_error "Could not retrieve the synopsis for the text to speech."
ls -al "${episode_summary_json}"
fi
REPLACE_LINE_1="This is Hacker Public Radio Episode ${ep_num}, for ${date}"
REPLACE_LINE_2="Today's show is entitled, \"${title}\""
REPLACE_LINE_3="The host is ${artist} and the duration is ${duration_iso8601}"
REPLACE_LINE_4="The flag is ${explicit}, and the license is ${license}"
REPLACE_LINE_5="The summary is \"${summary}\""
cp -v ${intro_srt} "${episode_intro_srt}"
cp -v ${outro_srt} "${episode_outro_srt}"
sed -e "s~REPLACE_LINE_1~${REPLACE_LINE_1}~g" -e "s~REPLACE_LINE_2~${REPLACE_LINE_2}~g" -e "s~REPLACE_LINE_3~${REPLACE_LINE_3}~g" -e "s~REPLACE_LINE_4~${REPLACE_LINE_4}~g" -e "s~REPLACE_LINE_5~${REPLACE_LINE_5}~g" -i "${episode_intro_srt}"
if [ "$( grep --count REPLACE_LINE "${episode_intro_srt}" )" -ne "0" ]
then
echo_error "The intro subtitles were not correctly generated \"${episode_intro_srt}\"."
fi
# Time shift the media subtitles on by the duration of the intro wav file
# https://trac.ffmpeg.org/wiki/UnderstandingItsoffset
itsoffset_intro="$( mediainfo --full --Output=JSON ${episode_intro_flac} | jq --raw-output '.media.track | .[] | select(."@type"=="Audio") | .Duration' | awk -F '.' '{print $1}' )"
if [[ -z "${itsoffset_intro}" || "${itsoffset_intro}" == "null" ]]
then
echo_error "Could not retrieve the itsoffset_intro to correct the timing of the subtitles."
fi
ffmpeg -hide_banner -loglevel error -y -itsoffset "${itsoffset_intro}" -i "${media%.*}.srt" -c copy "${episode_body_srt}"
# Timeshift the outro by the duration of the intro and the supplied media
itsoffset_body="$( mediainfo --full --Output=JSON "${episode_body_flac}" | jq --raw-output '.media.track | .[] | select(."@type"=="Audio") | .Duration' | awk -F '.' '{print $1}' )"
if [[ -z "${itsoffset_body}" || "${itsoffset_body}" == "null" ]]
then
echo_error "Could not retrieve the itsoffset_body to correct the timing of the subtitles."
fi
itsoffset_body=$((itsoffset_intro + $itsoffset_body))
ffmpeg -hide_banner -loglevel error -y -itsoffset "${itsoffset_body}" -i "${episode_outro_srt}" -c copy "${working_dir}/episode_outro_shifted.srt"
# Combine the intro, timeshifted media subtitles, and the timeshifted outro subtitles.
cat "${episode_intro_srt}" "${episode_body_srt}" "${working_dir}/episode_outro_shifted.srt" > "${episode_srt}"
# Parse the resulting subtitle file fixing the numberic counter
# https://en.wikipedia.org/wiki/SubRip
count=1
cat "${episode_srt}" | while read this_line
do
if [ "$( echo "${this_line}" | grep --count --perl-regexp '^[0-9]+$' )" -eq "1" ]
then
echo "${count}"
count=$((count+1))
else
echo "${this_line}"
fi
done > "${working_dir}/hpr${ep_num}.srt"
# extract the txt version
grep -Pv -- '-->|^$|^[0-9]+$' "${working_dir}/hpr${ep_num}.srt" > "${working_dir}/hpr${ep_num}.txt"
if [[ ! -s "${working_dir}/hpr${ep_num}.srt" || ! -s "${working_dir}/hpr${ep_num}.txt" ]]
then
echo_error "The transcriptions files were not generated."
ls -al "${working_dir}/hpr${ep_num}.srt" "${working_dir}/hpr${ep_num}.txt"
fi
}
#################################################
## Generate Final Report
function generate_final_report() {
echo_debug "Generating the final report. generate_final_report()"
if [[ -n "${skip_media_encoding}" && "${skip_media_encoding}" == "true" ]]
then
echo_debug "Skipping media creation from ()"
return
fi
check_variable_is_correct working_dir ep_num media_basename shownotes_edited synopsis
final_report="${working_dir}/hpr${ep_num}_report.html"
for this_file_extension_to_check in "${episode_formats[@]}"
do
if [[ ! -s "${working_dir}/hpr${ep_num}.${this_file_extension_to_check}" ]]
then
ls -al "${working_dir}/hpr${ep_num}.${this_file_extension_to_check}"
echo_error "The generated media is missing \"${this_file_extension_to_check}\"."
fi
done
if [[ ! -s "${working_dir}/${media_basename%.*}_media_report.html" ]]
then
ls -al "${working_dir}/${media_basename%.*}_media_report.html"
echo_error "The initial report is not available.\"${working_dir}/${media_basename%.*}_media_report.html\""
fi
grep -Pv '