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')
Related
Normally, when using ffmpeg, you define a name for the "pass" information to be stored in a file when enconding videos with 2 (or more) passes, e.g.:
ffmpeg -i "INPUT_FILE" -pass 1 -passlogfile videopass.log /dev/null -y && ffmpeg -i "INPUT_FILE" -pass 2 -passlogfile videopass.log -y "OUTPUT_FILE"
I'd love to be able to create a random pass name automatically in bash in the simplest way possible, preferably a "one-liner" using the system's default tools (Linux)... something like:
$(tr -dc A-Za-z0-9 </dev/urandom | head -c 8).log | ffmpeg -i "INPUT_FILE" -pass 1 -passlogfile > /dev/null -y && ffmpeg -i "INPUT_FILE" -pass 2 -passlogfile > -y "OUTPUT_FILE"
I'm too stupid to figure out a way of doing something similar that actually makes sense.
Thank you very much in advance!
Assuming the requirement is to create a random name for the password file:
$ pwdlog=$(mktemp XXXXXXXX.log) # have mktemp create a file with 8 random characters + ".log"
$ typeset -p pwdlog
declare -- pwdlog="Em6GeMdc.log"
$ ls -l "${pwdlog}"
-rw-------+ 1 username None 0 Apr 26 15:13 Em6GeMdc.log
This file could then be referenced in the ffmpeg call like such:
ffmpeg -i "INPUT_FILE" -pass 1 -passlogfile "${pwdlog}" ...
After countless tries and googling, I found a way to do exactly what I was looking for... A one-liner, nothing too complicated or fancy and using my system's default tools:
printf $RANDOM | xargs -i sh -c 'ffmpeg -i "INPUT_VIDEO" -pass 1 -passlogfile {} -an -f mp4 /dev/null -y && ffmpeg -i "INPUT_VIDEO" -pass 2 -passlogfile {} -y "OUTPUT_VIDEO"'
This will create a very simple 5-digit random number, which will be used as the initial name for the pass files in ffmpeg, ffmpeg automatically adds the file extension(s). Of course, you can (and probably should) add video/audio parameters to your ffmpeg commands. The important elements are the "printf $RANDOM | xargs -i sh -c" at the begining and the "{}" curly brackets after the "-passlogfile" command.
There are probably even simpler or more elegant ways of doing it, but this works exactly as I wanted, so I'm happy.
I am making mp4 files from a series of images. The images should run about 1 hour but I cannot get a full video because FFMPEG looks for a file it should not have.
[image2 # 0x55fe14bef700] Could not open file : /mnt/run/image-005437.jpeg
What is confusing is that the list that I pass to ffmpeg should not include that file. If my script does not like the result it sends the file to a sub directory in the target folder called failed
the location of the file with that number
$ pwd
run/failed
]$ ls
failed-005437.jpeg
the command I use to launch ffmpeg is the following
image_folder=$1
singularity exec --bind $work_dir:/mnt $work_dir/longleaf2.sif ffmpeg -f concat -safe 0 -y -i <(for f in /mnt/processed_images/$image_folder/image-%06d.jpeg; do echo "file '$f'";done) -vf "crop=trunc(iw/3)*2:trunc(ih/2)*2" -movflags +faststart /mnt/run/summary_files/${image_folder}.mp4
I have checked processed images and it is not there so why is ffmpeg looking for it?
pastebin of the failed run
https://pastebin.com/pF5ZefLf
My check that the file is not in the folder reference by the for loop so that it should never cause an error
$ ls image-005437.*
ls: cannot access image-005437.*: No such file or directory
Problem
When you run:
for f in /mnt/processed_images/$image_folder/image-%06d.jpeg; do echo "file '$f'";done
It will output:
file '/mnt/processed_images/foo/image-%06d.jpeg'
So then ffmpeg will use the sequence pattern type for the image demuxer. This expects a contiguous sequence.
Solution 1: Glob
Use a glob:
for f in /mnt/processed_images/$image_folder/*.jpeg; do echo "file '$f'";done
Now it will output each file. In this example image-000003.jpeg does not exist so it does not get listed:
file '/mnt/processed_images/foo/image-000001.jpeg'
file '/mnt/processed_images/foo/image-000002.jpeg'
file '/mnt/processed_images/foo/image-000004.jpeg'
Solution 2: Simplify and skip concat
Even better is to simplify your command by using the glob pattern type for the image demuxer within ffmpeg itself, and then you can avoid the concat demuxer:
image_folder=$1
singularity exec --bind "$work_dir":/mnt "$work_dir"/longleaf2.sif ffmpeg -pattern_type glob -framerate 25 -i "/mnt/processed_images/$image_folder/*.jpeg" -vf "crop=trunc(iw/3)*2:trunc(ih/2)*2,format=yuv420p" -movflags +faststart /mnt/run/summary_files/${image_folder}.mp4
The image demuxer glob pattern is not available for Windows users.
Added the -framerate input option for the image demuxer.
Added the format filter to make YUV 4:2:0 chroma subsampling for compatibility.
The variables have been quoted. See shellcheck.net.
FFmpeg 4.1 release branch is old. Download or compile a modern version before doing anything else.
The documentation for youtube-dl says I can run a post-process command with the --exec option.
Using Windows, here is an example I have tried:
youtube-dl --exec "ffmpeg -i {} -ac 2 -c:a libfdk_aac -cutoff 20000 -afterburner 1 -vbr 0 {}.m4a" https://www.youtube.com/watch?v=sw9DlMNnpPM
Note that {} passes the file name to the post-process command. For example filename.webm.
The problem is that {} includes the file extension.
How can I pass the file name to the post-process command without the file extension?
For example, if I were to convert the video, I would rather avoid getting an output name like filename.webm.m4a. Needless to say, I would rather want filename.m4a.
I'm new to shell script, trying to concat listed movies in a folder like:
filename="list.txt"
cat ${filename} | while read line;
do
ffmpeg -y -i sum.mov -i $line -filter_complex concat tmp.mov
cp tmp.mov sum.mov
done
However, this loop runs only one time.
What is the best practice?
Thanks
SOLVED:
#list.txt
file 'movie1.mov'
file 'movie2.mov'
file 'movie3.mov'
Don't need to write a shell script. Just
ffmpeg -f concat -i list.txt -c copy output.mov
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"