I'm trying to use ffmpeg to convert some .m4a audio files to .mp3, and have come across something that has me stumped. I'd like to create the .mp3 in the same location and with the same filename as the .m4a, and so I'm using a combination of find/exec and a bash script to do this, as follows:
find /Volumes/Untitled/ -name '[!.]*' -name '*.m4a' -exec ./m4atomp3.sh {} \;
where m4atomp3.sh looks like:
#!/usr/bin/env bash
[[ -f "$1" ]] || { echo "$1 not found" ; exit 1 ; }
P="$1"
echo "$P is the full filename"
filename=${P%.*}
echo "$filename is the stripped filename"
m4afilename=\"$filename.m4a\"
echo "$m4afilename is the input filename"
mp3filename=\"$filename.mp3\"
echo "$mp3filename is the output filename"
mycmd="/Users/nickstyles/Downloads/ffmpeg -i "$m4afilename" -codec:a libmp3lame -qscale:a 2 -nostdin "$mp3filename
echo $mycmd
$mycmd
Whenever I try this, it fails because ffmpeg doesn't find the file, seemingly because of the whitespace in the filename, e.g if the file was called /Volumes/Untitled/My M4As/My M4A.m4a I would see:
ffmpeg version N-99346-g003b5c800f-tessus https://evermeet.cx/ffmpeg/ Copyright (c) 2000-2020 the FFmpeg developers
built with Apple clang version 11.0.0 (clang-1100.0.33.17)
[configuration details]
"/Volumes/Untitled/My: No such file or directory
However, if I just paste what is returned by echo $mycmd into the command line, e.g:
/Users/nickstyles/Downloads/ffmpeg -i "/Volumes/Untitled/My M4As/My M4A.m4a" -codec:a libmp3lame -qscale:a 2 -nostdin "/Volumes/Untitled/My M4As/My M4A.mp3"
then it works absolutely fine. I'm sure I'm missing something very obvious, which hopefully someone can spot!
As Benjamin W. pointed out the problem was that the variable was still getting split by bash, due to WordSplitting, and the quotes I was adding to the content of the variable were not helping against this. The key was to ensure that the quotes were placed around the variable itself like:
m4afilename=$filename.m4a
echo "$m4afilename is the input filename"
mp3filename=$filename.mp3
echo "$mp3filename is the output filename"
/Users/nickstyles/Downloads/ffmpeg -i "$m4afilename" -codec:a libmp3lame -qscale:a 2 -nostdin "$mp3filename"
and now this works!
Try this : mycmd="/Users/nickstyles/Downloads/ffmpeg -i $m4afilename -codec:a libmp3lame -qscale:a 2 -nostdin $mp3filename"
In bash, you can put variable straight into double quotes.
Related
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
I have two folders "Left channel" and "Right channel". Each folder contains mono files with same names. Example: "Left channel" contains "A.wav", "B.wav", "C.wav" and "Right channel" contains "A.wav", "B.wav", "C.wav". I need to make stereo files for each mono files.
So I have to combine
ffmpeg -i left.mp3 -i right.mp3 -filter_complex "[0:a][1:a]join=inputs=2:channel_layout=stereo[a]" -map "[a]" output.mp3
and
for file in /dir/* do ffmpeg -i ...; done
How can I go through all mono files and make bunch of stereo files from these mono files with ffmpeg in bash?
Would you please try the following:
#!/bin/bash
lch="Left channel"; rch="Right channel" # directory names of wav files
for f in "dir/$lch/"*.wav; do
fname=${f##*/} # filename such as "A.wav"
outfile="output_${fname%.*}.mp3" # output filename such as "output_A.mp3"
if [[ -f dir/$lch/$fname && dir/$rch/$fname ]]; then
echo ffmpeg -i "dir/$lch/$fname" -i "dir/$rch/$fname" -filter_complex "[0:a][1:a]join=inputs=2:channel_layout=stereo[a]" -map "[a]" "$outfile"
fi
done
It just outputs the command line as a dry run. If the output looks good, drop echo and run again.
Please note the output of echo removes the double quotes around the filenames. If you copy the output of echo and execute it on the command line, it will not work well.
I have a basic command (ffmpeg -i input_file out.srt) to turn .mkv video files to .srt subtitle files. The problem is that I have to manually run the command for every .mkv file. So I tried to implement a for loop in Bash, but I keep getting errors.
#!/bin/bash
# ffmpeg -i input_file out.srt
for i in *.mkv ; do
ffmpeg -i "$i" "$(basename "${i/.mkv)")".str
sleep 30
done
The two errors I get are:
./subcon.sh: line 6: unexpected EOF while looking for matching `}'
./subcon.sh: line 9: syntax error: unexpected end of file
I am not familiar with Bash to understand whats going on. Does anyone know where I can look stuff up or how to solve this particular problem?
Problems:
Mismatched brackets. Change { to (.
Incorrect subtitle extension. Change str to srt.
basename syntax is incorrect. Change "$(basename "${i/.mkv)")".str to "$(basename "$i" .mkv)".srt. Or use Bash parameter expansion instead of basename.
New script:
#!/bin/bash
# ffmpeg -i input_file out.srt
for i in *.mkv ; do
ffmpeg -i "$i" "$(basename "$i" .mkv)".srt
sleep 30
done
You can eliminate basename:
#!/bin/bash
# ffmpeg -i input_file out.srt
for i in *.mkv ; do
ffmpeg -i "$i" "${i%.*}.srt"
sleep 30
done
I recommend shellcheck.net to check your Bash scripts.
#bin/bash
INPUT_DIR="$1"
INPUT_VIDEO="$2"
OUTPUT_PATH="$3"
SOURCE="$4"
DATE="$5"
INPUT="$INPUT_DIR/sorted_result.txt"
COUNT=1
initial=00:00:00
while IFS= read -r line; do
OUT_DIR=$OUTPUT_PATH/$COUNT
mkdir "$OUT_DIR"
ffmpeg -nostdin -i $INPUT_VIDEO -vcodec h264 -vf fps=25 -ss $initial -to $line $OUT_DIR/$COUNT.avi
ffmpeg -i $OUT_DIR/$COUNT.avi -acodec pcm_s16le -ar 16000 -ac 1 $OUT_DIR/$COUNT.wav
python3.6 /home/Video_Audio_Chunks_1.py $OUT_DIR/$COUNT.wav
python /home/transcribe.py --decoder beam --cuda --source $SOURCE --date $DATE --video $OUT_DIR/$COUNT.avi --out_dir "$OUT_DIR"
COUNT=$((COUNT + 1))
echo "--------------------------------------------------"
echo $initial
echo $line
echo "--------------------------------------------------"
initial=$line
done < "$INPUT"
This is the code I am working on.
The contents of file sorted_results.txt are as follows:
00:6:59
00:7:55
00:8:39
00:19:17
00:27:48
00:43:27
While reading the file it skips first two characters of the third line i.e. it takes it as :8:39 which results in the ffmpeg error and the script stops.
However when I only print the variables $INITIAL and $LINE, commenting the ffmpeg command the values are printed correctly i.e. same as the file contents.
I think the ffmpeg command is somehow affecting the file reading process or the variable value. BUT I CAN'T UNDERSTAND HOW?
PLEASE HELP.
Your bash read builtin command and the second ffmpeg command (for the audio) both read from STDIN, that is why they interfere with each other. You can either also specify -nostdin there or use another file descriptor (here number 3 is used) for read:
while IFS= read -r -u 3 line; do
...
done 3< "$INPUT"
I wrote a shell script to convert many video files and save them with something appended to the file name. The script works, but it seems to randomly skip files, and a lot of them.
When I re-run the script, it will convert files it skipped before. How can I get it to stop skipping files?
workingDir=/home/user/Videos
# get list of files to convert
find /video/folder -iname "*.mp4" > $workingDir/file_list
# convert files
cat $workingDir/file_list | while read LINE; do
# FFmpeg often cuts off the beginning of this line
echo "$(dirname "$LINE")/$(basename "$LINE")"
if /usr/bin/ffmpeg -n -loglevel panic -v quiet -stats -i "$LINE" \
-c:v libx264 -vf scale="trunc(oh*a/2)*2:320" \
-pix_fmt yuv420p -preset:v slow -profile:v main -tune:v animation -crf 23 \
"$(dirname "$LINE")/$(basename "$LINE" \.mp4)"_reencoded.mp4 2>/dev/null; then
echo "Success: $(dirname "$LINE")/$(basename "$LINE")" >> $workingDir/results
else
echo "Failed: $(dirname "$LINE")/$(basename "$LINE")" >> $workingDir/results
fi
done
One problem seems to be that FFmpeg interferes with the script. The FFmpeg output often cuts off the beginning of the next command, even if the output is not shown. This is demonstrated by the echo line before the if statement, which is often cut off. But even for lines that aren't cut off, most of them will be skipped for no apparent reason.
ffmpeg reads from stdin, thereby consuming input meant for while read. Just redirect stdin for ffmpeg by adding < /dev/null