Change name of audio file downloaded from youtube-dl - bash

I am writing a shell script to download audio.
I used youtube-dl -f 'bestaudio[ext=m4a]' <myurl>
The resulting file is the title along with-mSc76Q90C4.m4a where m4a is the format
I would like to set the filename during download and also have it in .wav format
PS: To covert to .wav, I tried youtube-dl -f 'bestaudio[ext=wav]' <myurl> but it did not seem to work. Error thrown is basically - cannot use specified format.
Here is the relavent part of my shell script
`read -p "Copy and paste the YouTube url" URL
echo $URL
read -p "Enter the song name" SONG
youtube-dl -f 'bestaudio[ext=m4a]' $URL`
I would like the string in $SONG to be the filename.
So if $SONG = "mysongname"
I want the file to be mysongname.wav

In the documentation of youtube-dl you'll find a --output option that lets you set the filename directly:
youtube-dl -f 'bestaudio[ext=m4a]' -o "$SONG" "$URL"
There's also a --audio-format option that seems to convert the extracted audio stream from a video to a given audio format but I'm not sure that it'll work for non-video files.

Related

handbrakecli/ffmpeg command to encode and auto filename(Ubuntu)

I have 2 preset .json files(from the GUI version on windows) to convert mkv to mp4.
converts to h264 and adds subtitle 1
converts to h264
I'm only trying to get no.2 to work at this stage.
for i in `*.mkv`; do HandBrakeCLI --preset-import-file HPRESET.json -Z "MYPRESET" --output *.mp4; done
no output name
HandBrakeCLI -i $1 --preset-import-gui HPRESET.json -Z "MYPRESET" --output $1.mp4
errors on output name
for i in `*.mkv`; do HandBrakeCLI --title $i --preset "Very Fast 1080p30" --output *.mp4; done
errors on output name AND not valid preset.
$ for i in `seq 4`; do HandBrakeCLI --input /dev/dvd --title $i --preset Normal --output NameOfDisc_Title$i.mp4; done
copied this from another stackoverflow question, but outputs as 1.mp4 and then 2.mp4 etc.
You can extract the filename without extension with something like that:
noext=${i%.*}
Example:
╰─$ for i in *.mkv; do echo "$i"; noext=${i%.*}; echo "$noext"; done
asdf.mkv
asdf
test.mkv
test
Same loop, different notation:
for i in *.mkv
do
#put the commands you want to run after "do" and before "done"
echo "$i"
noext=${i%.*}
echo "$noext"
done
Note that the for command will search any file in the current directory ending with .mkv. For each file it has found, it will save the files name into the variable $i, then execute the commands after do, then save the next files name into the variable $i and execute the commands between do and done. It will repeat that cycle until every file which has been found is processed.
As I have no experience with handbrake presets, here a solution with ffmpeg:
for i in *.mkv
do
#put the commands you want to run after "do" and before "done"
noext=${i%.*}
ffmpeg -i "$i" -c:v libx264 -c:a copy -c:s copy "$noext.mp4"
done

ffmpeg m4a/m4b/mp4 output file's "Time" value is incorrect when read into iTunes

I'm using ffmpeg to convert audiobooks to m4a/m4b/mp4. All seems to work until trying to play them in iTunes. It plays in VLC, QuickTime, and MacOS's Quicklook without issue.
"So why are you posting here? This isn't an iTunes forum."
I'm hoping this is iTunes being picky about file formats and that I can add some magic argument to my ffmpeg command and have it spit out something that iTunes can read.
Below is the bash function I'm using to do the conversion. I've tried m4a/mp4/m4b as values for TEMP_FILE_EXTENSION and tried opening the intermediate file as well. It's always the same corrupted "Time" value when you put it in iTunes.
dedrm_audible () {
# Check for AtomicParsley, ffmpeg, and 3 args
if (! type AtomicParsley >/dev/null 2>/dev/null) || (! type ffmpeg >/dev/null 2>/dev/null) || [ ! $# -eq 3 ]; then
echo "Usage:"
echo " dedrm_audible <path to .aax> <activation bytes> <path to output file>"
echo " Note: AtomicParsley and ffmpeg must be in PATH variable"
return
fi
local ORIGINAL_PWD="$(pwd)"
local TEMP_DIR="/tmp/audible"
local AUDIOBOOK_FILE="$1"
local ACTIVATION_BYTES="$2"
local OUTPUT_FILE="$3"
local FULL_AUDIOBOOK_PATH="$(realpath "${AUDIOBOOK_FILE}")"
local OUTPUT_PATH="$(realpath "${OUTPUT_FILE}")"
local TEMP_FILE_EXTENSION="m4a"
mkdir -p "${TEMP_DIR}"
cd "${TEMP_DIR}"
# Extract the book cover
ffmpeg -activation_bytes "${ACTIVATION_BYTES}" -i "${FULL_AUDIOBOOK_PATH}" -vcodec copy artwork.png
# Convert the audio
ffmpeg -activation_bytes "${ACTIVATION_BYTES}" -i "${FULL_AUDIOBOOK_PATH}" -vn -c:a copy -v debug output.${TEMP_FILE_EXTENSION}
# Add the cover to the new file
AtomicParsley output.${TEMP_FILE_EXTENSION} --artwork artwork.png --overWrite
# Put it where you want it and clean up
cp output.${TEMP_FILE_EXTENSION} "${OUTPUT_PATH}"
rm artwork.png
rm output.${TEMP_FILE_EXTENSION}
cd "${ORIGINAL_PWD}"
}
It goes off without a hitch. The new file is there waiting for me, with all the metadata including the cover when I do a "Get Info" or "Quick look" on it (I'm on MacOS). But when opening it and trying to play it in iTunes, the "time" field is way off and it immediately skips to the next song/audiobook in the queue.
Attempts to convert it within iTunes fail immediately - too quickly to see what's happening. The errors in the console simply say "Assert failure:" (with nothing after the colon).
edit: Tommy answered the question. Here's a working bash function:
dedrm_audible () {
# Check for AtomicParsley, ffmpeg, and 3 args
if (! type AtomicParsley >/dev/null 2>/dev/null) || (! type ffmpeg >/dev/null 2>/dev/null) || [ ! $# -eq 3 ]; then
echo "Usage:"
echo " dedrm_audible <path to .aax> <activation bytes> <path to output file>"
echo " Note: AtomicParsley and ffmpeg must be in PATH variable"
return
fi
local ORIGINAL_PWD="$(pwd)"
local TEMP_DIR="/tmp/audible"
local AUDIOBOOK_FILE="$1"
local ACTIVATION_BYTES="$2"
local OUTPUT_FILE="$3"
# Alternative to realpath (since I read somewhere that it's not there by default on some systems): OUTPUT_PATH="$( cd "$( dirname "$OUTPUT_FILE" )" && pwd )"
local FULL_AUDIOBOOK_PATH="$(realpath "${AUDIOBOOK_FILE}")"
local AUDIOBOOK_NAME="${$(basename "${FULL_AUDIOBOOK_PATH}")%.aax}.m4a"
local OUTPUT_PATH="$(realpath "${OUTPUT_FILE}")"
local TEMP_FILE_EXTENSION="m4a"
mkdir -p "${TEMP_DIR}"
cd "${TEMP_DIR}"
cp "${FULL_AUDIOBOOK_PATH}" "${AUDIOBOOK_NAME}"
# Extract the book cover
ffmpeg -activation_bytes "${ACTIVATION_BYTES}" -i "${AUDIOBOOK_NAME}" -vcodec copy artwork.png
# Convert the audio
ffmpeg -activation_bytes "${ACTIVATION_BYTES}" -i "${AUDIOBOOK_NAME}" -vn -c:a copy -v debug output.${TEMP_FILE_EXTENSION}
# Add the cover to the new file
AtomicParsley output.${TEMP_FILE_EXTENSION} --artwork artwork.png --overWrite
# Put it where you want it and clean up
mv output.${TEMP_FILE_EXTENSION} "${OUTPUT_PATH}"
rm artwork.png
rm "${AUDIOBOOK_NAME}"
cd "${ORIGINAL_PWD}"
}
They stopped working for me too, but if you just change the file extension to .m4a it will import fine, then you just have to change type from music to audiobook and tell it to remember location and it works fine on mac or iPhone.
The other approach is I went back and reran ffmpeg on the .aax with output to .m4b and iTunes handled it fine. I didn’t use.m4b before but I frankly don’t recall why.

Windows Batch: using user input as command argument

I recently discovered youtube-dl and I wanted to make a batch file within the folder of the youtube-dl executable that asks for a url and uses that url in the
youtube-dl --extract-audio --audio-format mp3 -i -o songs\%(title)s.%(ext)s <video URL>
command, which saves the video as an mp3 file in the \songs\ folder in the same filepath as the executable. The command works fine if I copy-paste it into a command prompt and replace <video url> with the actual url, but when I try to put this in a batch file (e.g. with set var1="") it uses the variable's name in the command (youtube-dl --extract-audio --audio-format mp3 -i -o songs\%(title)s.%(ext)s var1).
I've found this, and it seems to be what I'm looking for, but it didn't make any sense.
When copying the command into a batch file, you'll need to replace % with %%, so that the output template parameter to the -o option will be interpreted correctly (i.e. as the string %(title)s.%(ext)s).
If you prefer to be prompted to enter the link, batch file may look like this:
set /p var1= "Enter youtube link: "
youtube-dl --extract-audio --audio-format mp3 -i -o songs\%%(title)s.%%(ext)s %var1%
Or, just set var1 before calling the script (set var1=youtube-link), or in the script itself.

How to run ffmpeg in a loop and test it for error?

I have stuttering, seeking, and general playback issues when playing large mkv files through my Plex Media Server setup. I have been looking around for a way to automate scheduled tasks to move everything to mp4. The objective is:
Copy mkv files into mp4 preserving subtitles of every kind. Put the new file in the same subdir, and delete previous mkv version if conversion went successful.
When I tried to run ffmpeg on a loop, I run into the problem described here:
https://unix.stackexchange.com/questions/36310/strange-errors-when-using-ffmpeg-in-a-loop
This is my first adventure on shell scripting and I am pretty much stumbling around and trying to understand the syntax and philosophy of it. What I understand is that they use a file descriptor to redirect ffmpeg output to /dev/null.
The problem with that solution is that I would need to check ffmpeg output for errors to decide whether to delete the previous file or not. Furthermore, there is a common error when converting from picture based subtitles streams, which I circumvent by using a script I found (http://www.computernerdfromhell.com/blog/automatically-extract-subtitles-from-mkv/) to work after some modifications to my needs.
After much frustration I ended modifying the script so much that it does not serve to its purpose. It does not check for errors. Anyways, I will post it here. Mind you that this is my first shell script ever, and almost everything is confusing about it. The problem with this, is that I had to ditch my error checking and I am eliminating files that errored when converting. Losing the original without a valid copy.
#!/bin/bash
FOLDERS=( "/mnt/stg4usb/media0/test/matroska1" "/mnt/stg4usb/media0/test/season1" "/mnt/stg4usb/media0/test/secondtest")
FLAGS="-y -metadata title="" -c:v copy -map 0 -c:a libfdk_aac -ac 2 -movflags +faststart"
COUNTER=0
LOGFILE=batch-$(date +"%Y%m%d-%H%M%S").log
for FOLDER in "${FOLDERS[#]}"
do
echo "---===> STARTING folder: '$FOLDER'"
find $FOLDER -name "*.mkv" | while read line; do
OUTPUT=""
DATE=$(date +"%Y-%m-%d")
TIME=$(date +"%H:%M:%S")
COUNTER=$((COUNTER+1))
FILE=$(basename "$line")
DIR=$(dirname "${line}")
echo $'\n'$'\n'"[$COUNTER][$DATE][$TIME][FILE:'${line%.mkv}.mp4']"$'\n'
echo "#### Transcoding ####"'\n'
ffmpeg -i $line $FLAGS -sn "${line%.mkv}.mp4" < /dev/null
echo "#### Extracting subtitles ###"'\n''\n'
mkvmerge -i "$line" | grep 'subtitles' | while read subline
do
# Grep the number of the subtitle track
tracknumber=`echo $subline | egrep -o "[0-9]{1,2}" | head -1`
# Get base name for subtitle
subtitlename=${line%.*}
# Extract the track to a .tmp file
mkvextract tracks "$line" $tracknumber:"$subtitlename.$tracknumber.srt" < /dev/null
chmod g+rw "$subtitlename.$tracknumber"* < /dev/null
done
rm -frv "$line" < /dev/null
echo "Finished: $(date +"%Y%m%d-%H%M%S")"
done
echo '\n'"<===--- DONE with folder: '$FOLDER'"$'\n'$'\n' >> $LOGFILE
done
exit 0
So, basically, the idea is: run ffmpeg on a loop for all mkv under a directory and subdirectories (I was using find). Check it for all possible errors. If errors, try again without subtitles and extract the subtitles using mkvextract, else everything went ok, and delete the previous file.

check for file string if true create new file based on file string

Situation:
AVI files sometimes come in two files filename-cd1.avi and filename-cd2.avi.
I use the following line in a bash script on my iMac to copy the two cd files into a single AVI file:
MENCODER -ovc copy -oac copy *CD1.avi *CD2.avi -o "Joined Movie.avi" > /dev/null 2>&1
What I'd like to do is retain the base file name. So:
of file name with '-cd2' is found, like say: OldMovie-cd2.avi
What I'm wanting to do is store "OldMovie" as a variable so that I can tell mencoder to perform the copy on the two files and… Here's the kicker - I'd LOVE to have resulting file be "OldMovie.avi" which is the base name of the two cd sub files.
Make sense? Geez I hope so.
Appreciate the help, I've googled for hours without a solution.
Randy
for avi in *-cd2.avi; do
base=`echo $avi | sed 's+-cd2\.avi+$\.avi+g'`
echo base is $base
echo looking for ${base}-cd1.avi
if [ -f "${base}-cd1.avi" ]; then
echo Found ${base}-cd1.avi
MENCODER -ovc copy -oac copy ${base}-cd1.avi ${base}-cd2.avi -o "${base}.avi" > /dev/null 2>&1
else
echo Did not find ${base}-cd1.avi
fi
done

Resources