As I have a low-end computer running Linux I often need to re-encode HD videos, lowering the quality to be able to watch them in my machine. A typical case is when I download several episodes of a series, I can't convert them all at once, and I need to start re-encoding halfway through the series.
I typically use this line to convert a single episode to a lower quality:
avconv -i anime_episode_01.mkv -map 0 -c copy -c:v libx264 -crf 31 anime_01.mkv
If I were to batch-convert them at once I would use something like:
for i in *.mkv;do avconv -i "$i" -map 0 -c copy -c:v libx264 -crf 31 "encoded/$i";done
Where encoded is a subdirectory.
But what if I need to start re-encoding at, say, episode 5?
I have no idea.
There are probably lots of ways to do this, here are a couple of options.
Option 1: Use seq
Use seq to generate a sequence, loop over this and encode.
A sequence from 5 to 15:
seq 5 15
If you need to format the numbers, e.g. to get a 0 prefix for one digit numbers, you can
use the -f switch, which takes a printf style formatting argument of a float/double.
seq -f %02.0f 5 15
This can be used in a loop, e.g. something like this:
for i in $(seq -f %02.0f 5 15); do
filename="anime_episode${i}.mkv"
echo "Encoding episode $i: $filename"
avconv -i "$filename" -map 0 -c copy -c:v libx264 -crf 31 "encoded/$filename"
done
Option 2: Check whether encoded file exists
Do pretty much the same as you do in your current loop, but only perform encoding if
the encoded file does not already exist.
for i in *.mkv; do
if [ ! -f encoded/$i ]; then
echo "Encoding file: $i"
avconv -i "$i" -map 0 -c copy -c:v libx264 -crf 31 "encoded/$i"
else
echo "Skipped file: $i"
fi
done
Related
Can FFmpeg divide one big rectangular video into x smaller rectangular ones?
What would be a command for it?
Can we parametrize the command with number of rows and columns we desire?
Can we somehow prevent loosing pixel precision when command is provided with improper rows/column count for source video resolution?
This script does the job:
inputFile=$1
rows=$2
columns=$3
counter=0
for((r=0; r<$rows; r++))
do
for((c=0; c<$columns; c++))
doinputFile=$1
ffmpeg -i $inputFile -filter:v "crop=iw/$columns:ih/$rows:$c*
(iw/$columns):$r*(ih/$rows)" -vcodec libvpx -crf 5 -b:v 10M -an
"$(dirname "$inputFile")/$(basename "$inputFile" ".webm")$counter.webm"
((counter=counter+1))
done
done
There is a single-call solution to do single-in multiple-out like this:
ffmpeg -i inputFile \
-vsync vfr \
-filter_complex [0:v]untile=$ncolsx$nrows:$nout,select=mod(n\,$nout)+1:$nout[v1][v2][v3]...[v$nout] \
-map [v1] -r $fps output1.webm \
-map [v2] -r $fps output2.webm \
...
-map [v$nout] -r $fps output$nout.webm
Here, $nout = $ncols * $nrows and you need to set the output framerate $fps explicitly (or it defaults to $input_fps * $nout).
Accordingly, you can run your nested loops to form the FFmpeg command argument string, and call it once after the loop. Note that my use of pseudo-variables $xxx is not adhering to any language so please make necessary adjustments.
Using trial and error I am using the following script on an OSX, to bulk convert a whole folder full of mp3 files, to mp4, by looping a specific video file:
for i in *.mp3; do /usr/local/bin/ffmpeg -stream_loop -1 -i /path_to_filename.mp4 -c copy -v 0 -f nut - | /usr/local/bin/ffmpeg -thread_queue_size 10K -i - -i "$i" -c copy -map 0:v -map 1:a -shortest "$(basename "$i" )".mp4 ; done; for f in *.mp3.mp4; do mv -v "$f" "${f/.mp3.mp4/.mp4}"; done
How can I also print/add/burn the mp3 filename, without the extension (.mp3), as an additional video layer at the bottom of the generated video screen, and with the added difficulty of word wrapping the text if is too long?
This command makes a temporary SRT file for the subtitles filter which will automatically deal with the placement and word wrapping:
for i in *.mp3; do echo "1" > subs.srt; echo "00:00:00,000 --> 10:00:00,000" >> subs.srt; echo "${i%.*}" >> subs.srt; ffmpeg -stream_loop -1 -i video.mp4 -i "$i" -filter_complex "[0:v]subtitles=subs.srt:force_style=Alignment=3,format=yuv420p[v]" -map "[v]" -map 1:a -c:v libx264 -c:a copy -shortest -movflags +faststart "${i%.*}.mp4"; done
Running the command in the directory containing the file allows avoidance of basename and creates a simpler command.
MP3 is not universally supported in MP4. Consider changing -c:a copy to -c:a aac if your player does not support it. But I suspect you're targeting YouTube which will be fine with MP3 in MP4.
The three instances of echo are inefficient but effective in avoiding any newline issues. Using one instance of printf would be more optimal, but I don't have access to macOS to test its printf implementation.
Can I re-use 2 pass logs?
That is to say: I am doing this, but I wonder if I should be doing this. ie: does a -pass 1 that had different crf/b:v parameters from -pass 2 output produce the same results as always uniquely encoding both passes for every input?
I have a feeling I shouldn't be reusing pass1.
Say I am doing tests, and for the same input file produce multiple 2 pass ouputs with varying constrained bitrate/crf variants...
eg:
constrainedQ-br9M-crf12.webm
constrainedQ-br12M-crf18.webm
constrainedQ-br14M-crf18.webm
constrainedQ-br16M-crf18.webm
Is it ok to detect the prior log file, check that it was produced for the same input file, and re-use it by skipping -pass 1 for subsequent renders? (in which case ffmpeg finds the existing log and appears to use it for pass 2)
Or
Should I be re-generating the pass 1 log whenever the bitrate or crf changes?
[edit] everyone loves a bit of context code
f_rm2passFilesVP9() {
rm \
"${input%/*}/ffmpeg2pass-0.log" \
"${input%/*}/ffmpeg2pass-0.log.temp" \
"${input%/*}/ffmpeg2pass-0.log.mbtree.temp" &> /dev/null
}
...
f_2passLogForThisInputExists() {
if [[ "$input" == $(cat "${input%/*}/.priorInput" 2> /dev/null) ]];then
echo 1
else
echo 0
fi
}
...
if [[ 0 == $(f_2passLogForThisInputExists) ]];then
echo " ENCODING CONSTRAINED QUALITY br:$br crf:$CRF - PASS 1/2"
trap "f_rm2passFilesVP9" 1 2 3 6
ffmpeg -hide_banner -y -i "${input}" \
-c:v libvpx-vp9 -pass 1 -b:v "$br" -crf "$CRF" -threads 4 \
-tile-columns 6 -frame-parallel 1 \
-an -f webm /dev/null
echo "$input" >"${input%/*}/.priorInput"
trap "" 1 2 3 6
else
echo "REUSING - PASS 1 FOR THIS INPUT - PASS 1/2"
fi
echo "ENCODING CONSTRAINED QUALITY br:$br crf:$CRF - PASS 2/2"
ffmpeg -hide_banner -y -i "${input}" \
-c:v libvpx-vp9 -pass 2 -b:v "$br" -crf "$CRF" -threads 4 -speed 2 \
-tile-columns 6 -frame-parallel 1 -auto-alt-ref 1 -lag-in-frames 25 \
-c:a libopus -b:a 64k -f webm \
"${exportName}"
This should be OK if you are simply giving it more or less bitrate. I do this sometimes, but I also use a lot of zones, so I usually just rerun first pass because I know zones will definitely make a difference in the first pass file. If I add a zone after the first pass file is created the it will apply the difference in bitrate across the file and if I do it before then the zone bitrate modifier gets applied only to the frames specified. Since I think you are simply giving the file more or less bitrate then the distribution should be pretty much the same. I would say only rerun first pass if your second pass is going to much higher bitrate, like 20% or more. Better just to run first pass at the highest bitrate you intend to encode, if possible.
This question already has answers here:
bash script order of execution
(2 answers)
Closed 7 years ago.
I will be on 3 days vacation, so I would like to do a task in one file, after it's done, launch the other one using bash script, the way I would like to do is:
List files location in one file, i.e (toDo.txt)
Once the first file is done it goes to the other one.
Example:
doDo.txt contents:
/home/me/www/some_dir/file1F42.sh
/home/me/www/another_dir/fileD2cD.sh
/home/me/www/third_dir/fileG0IU.sh
/home/me/www/last_dir/fileVFpO.sh
file1F42.sh:
some commands here
Once is done, it should jump to line 2, which is: fileD2cD.sh
I do NOT want to use cron, because I do not know when the files will finish treatment, and at the same time I do NOT want to launch all of them at once.
This is a real example that I just finish to do:
ffmpeg -i Original/$domainName"_"$fileName"_"$f-Original.mp4 -strict experimental -vf "drawtext=fontfile='/usr/share/fonts/truetype/freefont/FreeSansBold.ttf':text='www.alfirdaous.com':x="$SizeX":y="$SizeY":fontsize="$textSize":fontcolor=$textColor" -vcodec libx264 -preset medium -crf 24 -acodec copy Done/"$domainName"_"$fileName"_$f-Done.mp4
mp4Box=$(MP4Box -add Done/"$domainName"_"$fileName"_$f-Done.mp4 "$domainName"_"$fileName"_$f.mp4)
echo $mp4Box >> ffmpeg_exec.log;
# Delete Done files
rm Done/"$domainName"_"$fileName"_$f-Done.mp4
# Get master thumbnail
ffmpeg -itsoffset -150 -i "$domainName"_"$fileName"_$f.mp4 -vcodec mjpeg -vframes 1 -an -f rawvideo -s 640x480 "$silsilaName"_$f.png
n=0
for offset in 140 160 180 200 220 240 260 280 300 320
do
printf -v outfile "$silsilaName"_"$f"_"%03d.png" "$((++n))"
ffmpeg -itsoffset -$offset -i $domainName"_"$fileName"_"$f.mp4 -vcodec mjpeg -vframes 1 -an -f rawvideo -s 640x480 "$outfile"
done
ffmpeg -i "$domainName"_"$fileName"_$f.mp4 -vn -ar 44100 -ac 2 -ab 128 -f mp3 $f.mp3
done
Last command line is:
ffmpeg -i "$domainName""$fileName"$f.mp4 -vn -ar 44100 -ac 2 -ab 128 -f mp3 $f.mp3
How do I know that it is DONE, finish, so I can go to my file list "doDo.txt" and start running the next file?
#! /bin/sh
while read file; do
echo "Executing $file"
sh "$file"
done < /dev/stdin
Where usage is <script.sh> < file_list
Does anyone know if it is possible to encode a video using ffmpeg in reverse? (So the resulting video plays in reverse?)
I think I can by generating images for each frame (so a folder of images labelled 1.jpg, 2.jpg etc), then write a script to change the image names, and then re-encode the ivdeo from these files.
Does anyone know of a quicker way?
This is an FLV video.
Thank you
No, it isn't possible using ffmpeg to encode a video in reverse without dumping it to images and then back again. There are a number of guides available online to show you how to do it, notably:
http://ubuntuforums.org/showthread.php?t=1353893
and
https://sites.google.com/site/linuxencoding/ffmpeg-tips
The latter of which follows:
Dump all video frames
$ ffmpeg -i input.mkv -an -qscale 1 %06d.jpg
Dump audio
$ ffmpeg -i input.mkv -vn -ac 2 audio.wav
Reverse audio
$ sox -V audio.wav backwards.wav reverse
Cat video frames in reverse order to FFmpeg as input
$ cat $(ls -r *jpg) | ffmpeg -f image2pipe -vcodec mjpeg -r 25 -i - -i backwards.wav -vcodec libx264 -vpre slow -crf 20 -threads 0 -acodec flac output.mkv
Use mencoder to deinterlace PAL dv and double the frame rate from 25 to 50, then pipe to FFmpeg.
$ mencoder input.dv -of rawvideo -ofps 50 -ovc raw -vf yadif=3,format=i420 -nosound -really-quiet -o - | ffmpeg -vsync 0 -f rawvideo -s 720x576 -r 50 -pix_fmt yuv420p -i - -vcodec libx264 -vpre slow -crf 20 -threads 0 video.mkv
I've created a script for this based on Andrew Stubbs' answer
https://gist.github.com/hfossli/6003302
Can be used like so
./ffmpeg_sox_reverse.sh -i Desktop/input.dv -o test.mp4
New Solution
A much simpler method exists now, simply use the command (adjusting input.mkv and reversed.mkv accordingly):
ffmpeg -i input.mkv -af areverse -vf reverse reversed.mkv
The -af areverse will reverse audio, and -vf reverse will reverse video. The video and audio will be in sync automatically in the output file reversed.mkv, no need to worry about the input frame rate or anything else.
On one video if I only specified the -vf reverse to reverse video (but not audio), the output file didn't play correctly in mkv format but did work if I changed it to mp4 output format (I don't think this use case of reversing video only but not audio is common, but if you do run into this issue you can try changing the output format). On large input videos that exceed the RAM available in your computer, this method may not work and you may need to chop up the input file or use the old solution below.
Old Solution
One issue is the frame rate can vary depending on the video, many answers depend on a specific frame rate (like "-r 25" for 25 frames per second). If the frame rate in the video is different, this will cause the reversed audio and video to go out of sync.
You can of course manually adjust the frame rate each time (you can get the frame rate by running ffmpeg -i video.mkv and look for the number in front of the fps, this is sometimes a decimal number like 23.98). But with some bash code you can easily extract the fps, store it in a variable, and automatically pass it to the programs.
Based on this I've created the following bash script to do that. Simply chmod +x it and run it ./make-reversed-video.sh input.mkv output.mkv. The code is as follows:
#!/bin/bash
#Partially based on https://nhs.io/reverse/, but with some modifications, including automatic extraction of the frame rate.
#Get parameters.
VIDEO_FILE=$1
OUTPUT_FILE=$2
TEMP_FOLDER=$3
echo Using input file: $VIDEO_FILE
echo Using output file: $OUTPUT_FILE
mkdir /tmp/create_reversed_video
#Get frame rate.
FRAME_RATE=$(ffmpeg -i "$VIDEO_FILE" 2>&1 | grep -o -P '[0-9\\. ]+fps' | grep -o -P '[0-9\\.]+')
echo The frame rate is: $FRAME_RATE
#Extract audio from video.
ffmpeg -i "$VIDEO_FILE" -vn -ac 2 /tmp/create_reversed_video/audio.wav
#Reverse the audio.
sox -V /tmp/create_reversed_video/audio.wav /tmp/create_reversed_video/backwards.wav reverse
#Extract each video frame as an image.
ffmpeg -i "$VIDEO_FILE" -an -qscale 1 /tmp/create_reversed_video/%06d.jpg
#Recombine into reversed video.
ls -1 /tmp/create_reversed_video/*.jpg | sort -r | xargs cat | ffmpeg -framerate $FRAME_RATE -f image2pipe -i - -i /tmp/create_reversed_video/backwards.wav "$OUTPUT_FILE"
#Delete temporary files.
rm -rf /tmp/create_reversed_video
I've tested it and it works well on my Ubuntu 18.04 machine on lots of videos (after installing the dependencies like sox). Please let me know if it works on other Linux distributions and versions.