Limit cpu limit of process in a loop - bash

I am trying to execute ffmpeg in a loop over multiple files. I only want one instance to run at a time, and to only use 50% of the cpu. I've been trying cpulimit but it isn't playing nice with the loop.
for i in {1..9}; do cpulimit -l 50 -- ffmpeg <all the options>; done
This spawns all nine jobs at once, and they are all owned by init so I have to open htop to kill them.
for i in {1..9}; do ffmpeg <all the options> & cpulimit -p $! -l 50; done
This hangs, ctrl+c continues to the next loop iteration. These instances can only be killed by SIGKILL.

Using a queue is the way to go. A simple solution that I use is Task Spooler. You can limit the number of cores ffmpeg uses with -threads also. Here's some code for you:
ts sh -c "ffmpeg -i INPUT.mp4 -threads 4 OUTPUT.mp4"
You can set the max number of simultaneous tasks to 1 with: ts -S 1
To see the current queue just run ts

You should run it in foreground. In this way the loop will work as expected.
$ cpulimit --help
...
-f --foreground launch target process in foreground and wait for it to exit
This works for me.
for file in *.mp4; do
cpulimit -f -l 100 -- ffmpeg -i "$file" <your options>
done

If you want the -threads option to have an effect on the encoder, you should put it after the -i argument, before the output filename - your current option only tells the decoding part to use a single thread. So to keep it all using a single thread, you want -threads 1 both before and after the -i option. so you can do it like:
ffmpeg -threads 1 -i INPUT.mp4 -threads 1 OUTPUT.mp4

Related

Bash read time run faster than what specified

This is the bash code
read -t 10 -p "Video path ==>" VIDEO_SOURCE
[ "$VIDEO_SOURCE" = q ]
if [ "$VIDEO_SOURCE" = "$NONE" ]; then
./run.sh
Basically what i wanted it to do is to run ./run.sh after 10 seconds if i didn't give it any input, the problem is that is runs the ./run.sh in less then 10 seconds.
Edit:
This is the full script
#! /bin/bash
VBR="1500k"
FPS="24"
QUAL="superfast"
RTMP_URL="rtmp://live.live"
KEY="xxx-xxx-xxx-xxx"
VIDEO_SOURCE="video.mp4"
while :; do
ffmpeg \
-re -f lavfi -i "movie=filename=$VIDEO_SOURCE:loop=0, setpts=N/(FRAME_RATE*TB)" \
-vcodec libx264 -pix_fmt yuv420p -preset $QUAL -r $FPS -g $(($FPS * 2)) -b:v $VBR \
-f flv "$RTMP_URL/$KEY"
read -t 10 -p "Video path ==>" VIDEO_SOURCE
[ "$VIDEO_SOURCE" = q ]
if [ -z "$VIDEO_SOURCE"]; then
./run.sh
fi
done
What i wanted it to do is to wait 10 seconds after a crash or after i press q , after 10 seconds either is going to run the new input if specified it, if the input is invalid or empty (in case i was too slow typing the input or in case of a crash) i wanted it to run ./run.sh, i'm running this script in tmux just to keep it running and if i want to update the input i just go back to the tmux session and press q and give it the new input.
I couldn't reproduce your problem. Nevertheless, if there really was such a strange problem, you could try to work around it using the following hack:
read var < <(timeout --foreground 10 head -n1)
This should behave somewhat like read -t 10 but relies on the external program timeout instead of read's built-in timeout functionality.

Including Youtube-dl in FFMPEG not working in Bash (OSX)

I am trying to download 5 second samples for a list of youtube video. The traditional approach is to download the entire file with "youtube-dl" and then use "ffmpeg" to split it however you want it.
I am trying to use the following method: https://github.com/ytdl-org/youtube-dl/issues/622#issuecomment-162337869
It does work when I include the variables in the command, for example:
ffmpeg -ss 0 -i $(youtube-dl -f best --get-url https://www.youtube.com/watch?v=ySVi-0RS5vI&t=5s) -t 10 -c:v copy -c:a copy title2.mp4
However, I am having issues trying to automate the system. Specifically, I would like ffmpeg and youtube-dl to read a file and use the values. I created the file "youtube.txt" which includes the following codes:
440.8,https://www.youtube.com/watch?v=0-4wOE_DNeA,661.2,881.6,0-4wOE_DNeA
330,https://www.youtube.com/watch?v=0-AMWW6tHzw,495,660,0-AMWW6tHzw
509.2,https://www.youtube.com/watch?v=0-Rmto2rgMw,763.8,1018.4,0-Rmto2rgMw
427.6,https://www.youtube.com/watch?v=0-U53qm45cA,641.4,855.2,0-U53qm45cA
320.4,https://www.youtube.com/watch?v=0-dja9Ys4Sg,480.6,640.8,0-dja9Ys4Sg
343.6,https://www.youtube.com/watch?v=0-g_PulsqtM,515.4,687.2,0-g_PulsqtM
415.6,https://www.youtube.com/watch?v=0-nniRyn7dU,623.4,831.2,0-nniRyn7dU
431.2,https://www.youtube.com/watch?v=006BQU3BFxw,646.8,862.4,006BQU3BFxw
I am using the following command:
parallel -j 6 --colsep ',' ffmpeg -ss {1} -i $(youtube-dl -f best --get-url {2}) --t 5 -c:v copy -c:a copy {5} :::: youtube.txt
However, I get the following errors:
ERROR: '{2}' is not a valid URL. Set --default-search "ytsearch" (or run youtube-dl "ytsearch:{2}" ) to search YouTube
--t: No such file or directory
Would you mind helping me?
Thanks!
Here's a solution using python2, so this should work on the python version shipped with MacOS. My original bash script was choking on the csv line reading for some reason. Add this script to getvids.py in the same directory as your youtube.txt, then run chmod +x getvids.py and when you're ready to turn it loose ./getvids.py
#!/usr/bin/python
import csv, os
with open('youtube.txt') as csv_file:
csv_reader = csv.reader(csv_file, delimiter=',')
for row in csv_reader:
starttimes = [row[0], row[2], row[3]]
yturl = os.popen('youtube-dl -f best --get-url '+row[1]).read().strip()
for thistime in starttimes:
print(row[1] + ' #time='+thistime)
os.system('ffmpeg -hide_banner -loglevel panic -ss '
+thistime+' -i "'+yturl+'" -t 5 -c copy '+row[4]+'['+thistime+'s].mp4')

Keep FFMPEG encoding session running

I am trying to encode an external HLS (m3u8) link into MPEG-TS over UDP via ffmpeg with this command:
ffmpeg -re -i http://example.com/index400.m3u8 -vcodec copy -acodec copy -f mpegts udp://127.0.0.1:10000?pkt_size=1316
Currently I am executing the command directly inside a terminal which I keep open on my Centos server. However, and after some time (volatile), I get the following error :
Failed to resolve hostname example.com: Temporary failure in name resolution
[hls,applehttp # 0x349b420] Failed to reload playlist 0
My question is, how can I run this command in a bash script or upstart or … so that whenever it unexpectedly stops, it automatically restarts.
I prefer not to use third parties like monit, and please be explicit in writing the script with annotation for newbies, I am not well experienced on this.
Turned out to be simpler than I thought. For future reference, this is what I did, in a terminal:
Create a new script:
vi test.sh
Insert the following code:
#!/bin/bash
while :
do
echo `ffmpeg -re -i http://example.com/index400.m3u8 -vcodec copy -acodec copy -f mpegts udp://127.0.0.1:10000?pkt_size=1316`
done
Press Esc W Q Enter to save and exit.
Execute the following commands:
chmod +x test.sh
./test.sh
And voilà, ffmpeg will automatically restart when an error occurs.

youtube-dl download one minute per every 5 minutes (on a twitch video, but i have the local file saved too if easier)

I would like to do what the title says
This is a ffmpeg command to download from a specific time in a video, offline or online.
ffmpeg -ss (stop time) -i (direct video link) -t (start time) -c:v copy -c:a copy (title.mp4)
I am going to be downloading this on OSX.
I dont care what the title is.
I think* there is a bash command that allows me to change the timings in this command up by a specific amount (+300 seconds per, the counter for start and stop time is in raw seconds)
So, bash script that runs that command but increases the start and stop times incrementally by 300 (the stop timing being 60+ seconds ahead), downloads, then repeats.
here it is:
contents of youtube-dl:
#!/usr/bin/env bash
# set start to 0, 300, 600... up to 72000 (20 hours)
for start in `seq 0 300 72000`; do
# set the outfile name
file="$2.$start.60.mp4"
ffmpeg -ss $start -i "$1" -t 60 -c:v copy -c:a copy "$file"
# get the duration of the last outfile
last_duration=`ffprobe -i "$file" -show_entries format=duration -v quiet -of csv="p=0"`
# if last outfile's duration isn't greater than a second, delete it and stop
[[ ! "$last_duration" -gt 1 ]] && rm -f $file && exit
done
then do:
chmod +x youtube-dl
usage:
./youtube-dl "http://your/movie.flv" title
ps: i discovered that your ffmpeg command was a little broken: it's -t (duration), not -t (start time).
refs:
ffmpeg usage (slhck, 2012)
ffprobe usage (ivan-neeson, 2014)

#/bin/sh in one line

I'm working on some Haskell project using FFmpeg. I need to batch create from a media folder with MP4 files and create screenshots from all of them. I got the code and am using it on a terminal in Unix. It works, but how do I make it in one line to be executed in system "xxxx" in Haskell?
If not using several system"xx"...
#/bin/sh
for i in $(ls *.mp4)
do
ffmpeg -i $i -vframes 7 -y -ss 10 -s 150x150 -an -sameq -f image2 -r 1/5 $i%1d.jpg
done
I tried:
import System.Cmd
function = do{system "#/bin/sh";
system "for i in $(ls *.mp4)";
system "do";
system "ffmpeg -i $i -vframes 7 -y -ss 10 -s 150x150 -an -sameq -f image2 -r 1/5 $i%1d.jpg";
system "done";}
but it gives a error:
-vframes: No such file or directory
/bin/sh: Syntax error: "done" unexpected
The problem is that you're trying to execute each line of your script as a separate, independent invocation of the shell. You just need to do it all with one system call, and separate each line of the script with \n:
system "for i in $(ls *.mp4)\ndo\n..."
but you can write the shell command on one logical line, instead:
system "for i in $(ls *.mp4); do ...; done"
The first line (which should be #!/bin/sh, by the way) is not necessary when using system.
I'm not sure why you want to use Haskell for this purpose, though, if you're just going to execute a single shell script. You should write the loop over the directory contents in Haskell, and only call out to the system to do an individual conversion. At the very least, you should probably put this script into its own file and invoke it with system "sh convert.sh" or similar.
(If you want a more convenient syntax for multi-line strings like these scripts in Haskell, try the interpolatedstring-perl6 or string-qq packages.)
First, It's #!/bin/sh. Notice the exclamation mark.
Second, you're trying to execute a series of commands one after another, so no state is kept between them. Try to execute it as a single command:
function = system "for i in $(ls *.mp4); do ffmpeg -i $i -vframes 7 -y -ss 10 -s 150x150 -an -sameq -f image2 -r 1/5 $i%1d.jpg; done"
Another option is to save your whole script, with the #! corrected, as a .sh file, make it executable and:
function = system "./myscript.sh"
Bash 4.X Solution
system "/bin/bash -c 'shopt -s globstar; for i in **.mp4; do ffmpeg -i \"$i\" -vframes 7 -y -ss 10 -s 150x150 -an -sameq -f image2 -r 1/5 \"$i\"%1d.jpg; done'"
You don't need #!/bin/bash with system (don't forget the bang !)
Quote your variables otherwise files with spaces in their names wont work
Don't use ls like that, it will break when it comes across a file with spaces in its name
Posix Solution
system "find /some/path -type f -name \"*.mp4\" -exec sh -c 'for f; do ffmpeg -i \"$f\" -vframes 7 -y -ss 10 -s 150x150 -an -sameq -f image2 -r 1/5 \"$f%1d.jpg\"; done' _ {} +"
You should not echo the shell script like this but create a shell command like this:
system "for i in $(ls *.mp4); do ffmpeg -i $i -vframes 7 -y -ss 10 -s 150x150 -an -sameq -f image2 -r 1/5 $i%1d.jpg; done"

Resources