Incorporating ffmpeg in a bash script - bash

I have a very large audio mp4 file that contains several songs.
I have generated a script which reads a text file with the times and the song names and successfully assigns starttime, endtime and songtitle in 3 variables. The script successfully echoes the variables and returns the following format:
00:00:00 00:10:15 Song1
00:10:15 00:14:20 Song2
and so on...
Now I am intending to use this script with ffmpeg and crop each part of the big file into smaller audio files.
The script thus, after feeding the variables in a while loop, it reaches to the command
ffmpeg -ss $START -t $END -i ${1} -acodec copy $SONGNAME.mp4
Once I run the script, the first two songs are cropped, but then the whole process stops with
Press [q] to stop, [?] for help
error parsing debug value
debug=0
I checked the generated files and they play ok, but there is no way for me to know why the script stopped there and did no proceed to the rest of the file (considering that when in the script I replace ffmpeg with echo, the script echoes the variables flawlessly).
In other words I don't know if there is a problem in my script, ffmpeg, or the source music file.

In this case I would add the argument -nostdin to ffmpeg.

Related

Bash single quote in filename and ffmpeg escaping

I want to convert a list of flac files to mp3 using ffmpeg.
I have written the list of files to convert in a file.
Here is my script
#!/bin/bash
while read -r line
do
ffmpeg -i "$line" -ab 320k "${line%.flac}.mp3"
done < flac_list
It works, however when a filename contains a single quote, it does not work.
And here begins my escaping nightmare.
I have found dozens of combinations without find how to make it work.
Could someone help ?
Thanks to #chepner, the -nostdin flag appended to ffmpeg solves the issue.
I have found too that ffmpeg has surprising problems sometimes if it encounters certain characters in the filename. I stumbled over this problem when converting m4a to mp3 using a script. I didn't know that an innocent single-quote is one of them.
What you can do - aside of reporting a bug to ffmpeg - is to test, whether your filename contains an unwanted character, and either rename the file or create a symbolic link to the file, using a "good" name, and undo these changes when your conversion is done.

AppleScript + QuickTime — batch trim + create edit + export video

I'm looking for some help.
What I'm trying to do is to have AppleScript open a folder of videos, then get QuickTime to randomly trim them (as in the start frame from where it trims) and then have a variable length of the trim itself (say random between 1sec to 2sec as boundaries for the new 'clip'). After trimming and creating the new 'edit' (it would add this new random trimming to all the videos in the folder then add to timeline). QT then needs to export the edit to a new folder.
In summary, trying to make a quick auto-editing app that can just pick random selects from a folder full of videos then save an edit to a new video.
ANY help with this would be hugely appreciated!
I've been trying but to nail avail (I'm fairly new to coding to going around in circles).
Thanks!
Dylan
Like #CJK, I would recommend you use bash and ffmpeg as they are both available on macOS and both better known and more widely applicable than Applescript and QuickTime.
IMHO, as Apple does not ship a package manager, you would be well advised to use homebrew to install, update and remove packages. It is available on the homebrew website.
Once you have that, you can find any package you want with:
brew search packageXYZ
So, you can now install ffmpeg with:
brew install ffmpeg
Now you would want a bash script that:
loops through all your ".mov" files in a directory
gets their lengths
calculates a random duration
calculates a random start time
extracts that piece of video as a clip
and, finally at the end, assembles all the clips together
That will look something like this, which I would suggest you save in a bash script called $HOME/RandomClips
#!/bin/bash
# Set up globbing
shopt -s nullglob nocaseglob
# Clear list of files we are going to concatenate
> list.txt
N=1
# Loop through all ".MOV" files
for f in *.MOV ; do
# Tell user which one we are processing
echo Processing file $f
# Get length of this video in seconds
duration=$(mdls -raw -name kMDItemDurationSeconds "$f")
echo ... Duration: $duration
# Generate a random clip length less than 5 seconds
((seconds=RANDOM%5))
echo ... Clip length: $seconds
# Generate start time
((start=RANDOM%(duration-seconds)))
echo ... Start time: $start
# Extract clip into file called "Clip-1.mov", "Clip-2.mov" etc
clipname="Clip-$N.mov"
echo ... Extracting $clipname
ffmpeg -hide_banner -ss $start -i "$f" -t $seconds -pix_fmt yuv420p "$clipname"
# Add name of this clip to the list of files to concatenate at the end
echo "file $clipname" >> list.txt
# Increment clip counter
((N=N+1))
done
# Now join together all the extracted clips into a single file
ffmpeg -hide_banner -f concat -i list.txt -c copy -pix_fmt yuv420p mergedVideo.mov
Now you need to make that executable, just necessary once, with:
chmod +x $HOME/RandomClips
Then use cd to navigate to a directory of movies:
cd some/place/with/movies
and run the script with:
$HOME/RandomClips
The script is not the most robust or well-tested in the entire world but it should be 90+% good. You may have to resize videos to a fixed size so they all match formats. Likewise with codecs. Probably ask another question if that becomes an issue.
No-one said answers have to be 100% perfect, and as no-one else has suggested anything, this will hopefully get you well on your way.
If you want to debug the script, you can:
read the debug output line by line
check all the extracted clips look correct, i.e, "Clip-1.mov", "Clip-2.mov"
read the file called "list.txt" to see if it contains all the clips

Output to pipe and file at the same time even if pipe isn't accepting inputs

So, I made a script for Cygwin that uses Windows's ImageMagick and FFmpeg, but I am not sure if the results here will also apply for bash on Linux. So, what the script does is I have some cartoon video files and I'm using Waifu2x to enhance and upscale the images to 4K, and then using ImageMagick to pipe it to FFmpeg, which is also used to resize it to 3840x2160 in case the resolution is slightly different. Here's a small script I wrote for this example to simplify how it outputs to FFmpeg, as the real script is extremely lengthy and complex.
#!/bin/bash
fun(){
convert out.png JPG:-|tee "$outfile"
}
fun|ffmpeg -f image2pipe -r 60 -i - -c:v libx265 -movflags +faststart "$outputfile"
Now, what I noticed is that if FFmpeg fails to encode, the function continues but fails to output to $outfile. What I want to do is have it able to output to that file in case the encoding fails since I also write all the images to a cache folder for FFmpeg to run through in case the encoding fails, but I also want to write to both the pipe for FFmpeg and the file at the same time. What seems to be happening is that the command tee appears to be refusing to write to the file if it can't write to the pipe. I'm not sure if this behavior is intended, and/or if it also does this on Linux bash. How can I get around this and have it write to the file even if it can't write to the pipe, but write to both at the same time rather than writing to the file and attempting to read it back to the pipe?
Have you tried tee with the -p option? It makes tee continue writing even if tee can't write to its standard output, which in your case means it should cope if ffmpeg fails.
fun() {
convert out.png JPG:- | tee -p "$outfile"
}

passing script variable of filename with spaces in bash to external program (ffmpeg) fails

Short story: I'm trying to write a script that will use FFmpeg to convert the many files stored in one directory to a "standard" mp4 format and save the converted files in another directory. It's been a learning experience (a fun one!) since I haven't done any real coding since using Pascal and FORTRAN on an IBM 370 mainframe was in vogue.
Essentially the script takes the filename, strips the path and extension off it, reassembles the filename with the path and an mp4 extension and calls FFmpeg with some set parameters to do the conversion. If the directory contains only video files with without spaces in the names, then everything works fine. If the filenames contain spaces, then FFmpeg is not able to process the file and moves on to the next one. The error indicates that FFMpeg is only seeing the filename up to the first space. I've included both the script and output below.
Thanks for any help and suggestions you may have. If you think I should be doing this in another way, please by all means, give me your suggestions. As I said, it's been a long time since I did anything like this. I'm enjoying it though.
I've include the code first followed by example output.
for file in ./TBC/*.mp4
do
echo "Start of iteration"
echo "Full text of file name:" $file
#Remove everything up to "C/" (filename without path)
fn_orig=${file#*C/}
echo "Original file name:" $fn_orig
#Length of file name
fn_len=${#fn_orig}
echo "Filename Length:" $fn_len
#file name without path or extension
fn_base=${fn_orig:0:$fn_len-4}
echo "Base file name:" $fn_base
#new filename suffix
newsuffix=".conv.mp4"
fn_out=./CONV/$fn_base$newsuffix
echo "Converted file name:" $fn_out
ffmpeg -i $file -metadata title="$fn_orig" -c:v libx264 -c:a libfdk_aac -b:a 128k $fn_out
echo "End of iteration"
echo
done
echo "Script completed"
With the ffmpeg line commented out, and two files in the ./TBC directory, this is the output that I get
Start of iteration
Full text of file name: ./TBC/Test file with spaces.mp4
Original filename: Test file with spaces.mp4
Filename Length: 25
Base filename: Test file with spaces
Converted file name: ./CONV/Test file with spaces.conv.mp4
End of iteration
Start of iteration
Full text of file name: ./TBC/Test_file_with_NO_spaces.mp4
Original file name: Test_file_with_NO_spaces.mp4
Filename Length: 28
Base file name: Test_file_with_NO_spaces
Converted file name: ./CONV/Test_file_with_NO_spaces.conv.mp4
End of iteration
Script completed
I won't bother to post the results when ffmpeg is uncommented, other than to state that it fails with the error:
./TBC/Test: No such file or directory
The script then continues to the next file which completes successfully because it has no spaces in its name. The actual filename is "Test file with spaces.mp4" so you can see that ffmpeg stops after the word "Test" when it encounters a space.
I hope this has been clear and concise and hopefully someone will be able to point me in the right direction. There is a lot more that I want to do with this script such as parsing subdirectories and ignoring non-video files, etc.
I look forward to any insight you can give!
try quoting you output file:
ffmpeg -i "$file" ... "$fn_out"
bash separates arguments based on spaces, so you have to tell him that $fn_out is one single argument; whence the "" to show that this is one argument.
There is another edge-case where spaces break bash for loops.
"BASH for loop works nicely under UNIX / Linux / Windows and OS X while working on set of files. However, if you try to process a for loop on file name with spaces in them you are going to have some problem. For loop uses $IFS variable to determine what the field separators are. By default $IFS is set to the space character..."
https://www.cyberciti.biz/tips/handling-filenames-with-spaces-in-bash.html
Before:
for file in $(find . -name '*.txt'); do echo "$file"; done
Outputs:
./files/my
documents/item1.txt
./files/my
documents/item2.txt
./files/my
documents/item3.txt
Therefore you should set IFS to ignore spaces.
After:
IFS=$'\n'
for file in $(find . -name '*.txt'); do echo "$file"; done
Outputs:
./files/my documents/item1.txt
./files/my documents/item2.txt
./files/my documents/item3.txt

Learning Shell - creating a script with parameters that runs two separate cli apps

I want to learn shell script, so I'm trying to download a youtube video using youtube-dl then convert it to mp3 using ffmpeg.
I do it manually running youtube-dl http://youtube.com/watch?v=...
then
ffmpeg -i downloadedFile -ab 256000 -ar 44100 audioFile.mp3.
I know that I need to pass two arguments to my script, one for the video url and another for the audio file to keep things as simple as possible, but I don't know how to start. Maybe grep the video id in the url and using it to know which file to use to convert into mp3? (since youtube-dl saves the video named by it's id)
Can someone recommend me an article or documentation that can help me?
You can use the --output parameter of youtube-dl to have an arbitrary template. Additionally, youtube-dl can already convert to mp3! Try
#!/bin/sh
youtube-dl -o '%(title)s.%(ext)s' -x --audio-format mp3 -- "$1"
-o or --output defines the output name. You can use a number of templates, including %(title)s for the title of the video, %(ext)s for the extension, and %(id)s for the video ID. You can also use static filenames such as 'audio.%(ext)s, which will result in anaudio.mp3` file.
-x or --extract-audio advises youtube-dl to convert the video to an audio file (and remove the video file afterwards unless you pass -k). However, in contrast to your solution, youtube-dl will not recode audio streams that are already in mp3 - even with a relatively high bitrate such as 256k, you'll lose quality when you decode mp3 and re-encode it afterwards.
If you want a specific bitrate, use the --audio-quality parameter, say --audio-quality 256k.
The --audio-format parameter advises youtube-dl to convert audio to the given format. You can use best to always get the original audio (and not lose any quality), in whatever format it is.
"$1" is the first parameter of your shell script. You can pass in whole URLs, video IDs, or some shortcuts (like ytsearch:python to search YouTube for "python" and pick the first video.

Resources