I have a library of video files. They get moved around, zipped, unzipped and stuff.
It happened, that some of the files get e.g., transferred/ extracted only partially. This problem usually shows up only when actually watching that video (i.e., the video stops prematurely, which is then really annoying).
Is there a way to batch-verify the integrity of a video library?
I came up with the following, inspired by this question:
find . -regex ".*\.\(avi\|mkv\)" -exec ffmpeg -v error -i {} -f null - \;
The problem here is, that ffmpeg does not include the file name when printing the error messages which means I do not know which file in the batch is erroneous.
To make a long story short:
Is there a way to include the file name in the ffmpeg error messages?
Simply capture the output of ffmpeg and print it out with a proper header if not empty:
find . -regex ".*\.\(avi\|mkv\)" | while read f; do
ffmpeg_out=$(ffmpeg -hide_banner -nostdin -v error -i "$f" -f null - 2>&1)
[[ $ffmpeg_out ]] && echo -e "==> ERROR in $f\n${ffmpeg_out}"
done
I've added a couple of ffmpeg options to ensure proper operations:
-hide_banner turns off the normal FFmpeg preamble, which is just unnecessary noise
-nostdin tells FFmpeg to ignore any (accidental) keyboard interaction
Related
Every one of my music folders are set up like Artist > Year Album >
Track 01.flac
Track 02.flac
Track 03.flac
folder.jpg, jpeg, png, etc
And what I need to do is if folder.* is available.
if [ -f folder.* ]; then
Run this command to set smaller size without replacing the original photo.
for small in folder.*
convert $small -resize 1000x1000 temp$small
Then run these commands on every file to automatically add the smaller sized cover to each audio file's tagging.
ffmpeg -i TRACK.flac -i SMALLFOLDER.* -map a -map 1:v -disposition:v attached_pic -metadata:s:v comment="Cover (Front)" -codec copy TRACKWITHART.flac
&& rm TRACK.flac
&& mv TRACKWITHART.flac TRACK.flac
&& rm temp$small
Last little bit there is me cleaning up. I'm having trouble piping commands into one another with this and not the most experienced with that sort of thing.
And also, if it's not available like above, will need to extract it from the first audio file by finding it.
else
find . -name "*.flac" -print -quit
And extracting it with this command.
ffmpeg -i TRACK.flac -vf scale=1000:1000 -an FOLDER.png
Then run the other commands above.
Now I don't know if anyone is familiar with FFmpeg but it's actually kind of nightmare because it's not necessarily for audio tagging but I don't know anything else to handle this kind of automated album art task in the terminal. If anyone can point me more in the right direction with a better CLI utility, that'd be awesome or just help with this bash scripting. You can see I'm fairly familiar with the terminal and getting some things done by searching the web but putting them altogether in a bash script is very difficult for me to understand, if anyone has some links for specifically this, that would be much appreciated.
You have the general right idea of how to do it.
The wooledge BashGuide is pretty much the best place to start when learning bash scripting. It's very accessible, and it directly addresses a lot of the pitfalls that beginners are susceptible to when writing scripts.
ALWAYS quote your variables when you are using them to store filenames/paths. You need to write your script as if every path/filename will have spaces, newlines, special characters, etc. Quoting your variables will go a long way towards preventing any chaos when your script runs.
Here is your code fixed up and thrown together into a working script:
#!/bin/bash
# check for album art file,
# if none, extract from first flac w/ ffmpeg
# exit script if ffmpeg fails
[[ -f folder.* ]] ||
{ tracks=(*.flac)
ffmpeg -i "${tracks[0]}" -vf scale=1000:1000 -an folder.png \
|| exit 1 ; }
# define an array of all folder.* files
albumart=(folder.*)
ffmpeg -i "${albumart[0]}" -vf scale=1000:1000 "tmp_${albumart[0]}" \
|| exit 1
# use the first element of the array,
# in case there are multiple folder.* files.
# exit if ffmpeg gives error code
for track in *.flac; do
ffmpeg -i "$track" -i "tmp_${albumart[0]}" -map a -map 1:v -disposition:v attached_pic -metadata:s:v comment="Cover (Front)" -codec copy "tmp_${track}" \
&& rm "$track" \
&& mv "tmp_${track}" "$track"
done
rm "tmp_${albumart[0]}"
I took the liberty of changing your convert line of image-resizing code so that it is instead handled by ffmpeg, since I am unfamiliar with "convert". If it is a script or binary you use, you will want to edit this line (keeping the new input & output variables intact).
This script does not need any arguments, and it will loop through and add the album art & metadata to all .flac files in your current directory. It is not designed to work recursively; you will need to cd into & run the script in each directory.
Disclaimer: I am just a guy who googled most of his knowledge when it was needed and there might be no cohesion at all. Sorry for this in advance.
I am writing a small script on my Ubuntu 18.04 server that should run a loop for encoding raw DVR files into easy to handle mp4. This "hard" part works fine already, I am just trying to get a clean script to let it run by the full hour. Unfortunately for whatever reason bash doesn't accept my if statement.
I tried dos2unix, semicolons, spacing, tabbing, intendation before and after the if statement but nothing worked. I scavenged almost all google results and stackoverflow-posts but nothing worked. I checked for hidden characters that could break the script. Still the error.
In the following script the XXX are placeholders for private stuff. Those lines already work.
#!/bin/bash
for file in $(find /var/www/vhosts/XXXXXX/* -name "*.h264");
if [ -z "$file" ]
then
echo "No new h264-files found. Exiting."; break
fi
do
echo "H264-files found. Calling ffmpeg for conversion to mp4."
ffmpeg -y -i "$file" -c:v libx264 -preset veryfast -crf 26 -an -sn "${file/h264/mp4}"
echo "Ffmpeg is finished."
#rm "$file" disabled until scripts works perfectly and is callable from plesk.
echo "All new files converted. Refreshing XXXXXcloud files."
sudo -u XXXX php /var/www/vhosts/XXXXX
echo "Script done."
done
I keep getting the following:
ffmpeg_transcode_h264tomp4.sh: line 3: syntax error near unexpected token `if'
ffmpeg_transcode_h264tomp4.sh: line 3: `if [ -z "$file" ]'
Using the ifne util and some advice from Charles Duffy, this should meet the spec:
find /var/www/vhosts/XXXXXX/* -name "*.h264" -print0 |
ifne -n echo "No new h264-files found. Exiting." |
while IFS= read -r -d '' file ; do
# ... more code goes here...
done
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.
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
The following works very nicely to determine the length of various audio/video files:
mplayer -identify file.ogg 2>/dev/null | grep ID_LENGTH
However, I want to kill mplayer's output so I can determine the length of many files more efficiently. How do I do that?
The MPlayer source ships with a sample script called midentify, which looks like this:
#!/bin/sh
#
# This is a wrapper around the -identify functionality.
# It is supposed to escape the output properly, so it can be easily
# used in shellscripts by 'eval'ing the output of this script.
#
# Written by Tobias Diedrich <ranma+mplayer#tdiedrich.de>
# Licensed under GNU GPL.
if [ -z "$1" ]; then
echo "Usage: midentify.sh <file> [<file> ...]"
exit 1
fi
mplayer -vo null -ao null -frames 0 -identify "$#" 2>/dev/null |
sed -ne '/^ID_/ {
s/[]()|&;<>`'"'"'\\!$" []/\\&/g;p
}'
The -frames 0 makes mplayer exit immediately, and the -vo null -ao null prevent it from trying to open any video or audio devices. These options are all documented in man mplayer.
FFMPEG can give you the same information in a different format (and doesn't attempt playing the file):
ffmpeg -i <myfile>
There's another FF-way in addition to #codelogic's method, which doesn't exit with an error:
ffprobe <file>
and look for the duration entry.
Or grep for it directly in the error stream:
ffprobe <file> 2> >(grep Duration)
looks like there are a few other libs available, see time length of an mp3 file
Download your .mp3 file, play it with your Player (ex. Windows Media Player) and the player will show the total time at the end of play.