I use the following to merge video in numeric order.
for f in *; do mv "$f" "${f: -17}"; done &&. find *.ts|. sed 's:\:\ :g'| sed 's/^/file /' > fraglist.txt && ffmpeg -f concat -safe 0 -i fraglist.txt -c copy output.ts; rm fraglist.txt
This works great for files named like the following...
000001
000002
000003
000004
000005
000006
000007
000008
000009
000010
But If I need something like the following merged the order is based on how many digits there are in the file name...
1708.ts 9803.ts 13798.ts 17815.ts 21804.ts 25819.ts
29832.ts
What command could I use to get the second group of files merged in that order? Thank you for your help!
Simplify your whole process by using printf alone to make the txt file contents, and use sort to provide natural/version sorting:
printf "file '%s'\n" *.ts | sort -V > fraglist.txt
ffmpeg -f concat -i fraglist.txt -c copy output.ts
Result:
file '1708.ts'
file '9803.ts'
file '13798.ts'
file '17815.ts'
file '21804.ts'
file '25819.ts'
file '29832.ts'
No need to rm fraglist.txt as the sort redirect (>) will overwrite fraglist.txt.
Note that I removed -safe 0 (an option specific to the concat demuxer) from the ffmpeg command because you don't need it in this exact example. But if you get the Unsafe file name error (such as due to special characters including spaces) then you will need to add it.
Related
I have a directory of mp3 files which are all named 1.mp3, 2.mp3 etc..
#dir with numbers for names
10.mp3 15.mp3 2.mp3 24.mp3 29.mp3 33.mp3 38.mp3 42.mp3 47.mp3 51.mp3 56.mp3 60.mp3 65.mp3 7.mp3 74.mp3 79.mp3 83.mp3 88.mp3 92.mp3
11.mp3 16.mp3 20.mp3 25.mp3 3.mp3 34.mp3 39.mp3 43.mp3 48.mp3 52.mp3 57.mp3 61.mp3 66.mp3 70.mp3 75.mp3 8.mp3 84.mp3 89.mp3 93.mp3
12.mp3 17.mp3 21.mp3 26.mp3 30.mp3 35.mp3 4.mp3 44.mp3 49.mp3 53.mp3 58.mp3 62.mp3 67.mp3 71.mp3 76.mp3 80.mp3 85.mp3 9.mp3 94.mp3
13.mp3 18.mp3 22.mp3 27.mp3 31.mp3 36.mp3 40.mp3 45.mp3 5.mp3 54.mp3 59.mp3 63.mp3 68.mp3 72.mp3 77.mp3 81.mp3 86.mp3 90.mp3 95.mp3
14.mp3 19.mp3 23.mp3 28.mp3 32.mp3 37.mp3 41.mp3 46.mp3 50.mp3 55.mp3 6.mp3 64.mp3 69.mp3 73.mp3 78.mp3 82.mp3 87.mp3 91.mp3 96.mp3
I wrote a for loop to extract the title from the metadata using ffmpeg:
for x in *.mp3; do
ffmpeg -i $x ./$("ffmpeg -i $x 2>&1 |grep -E '^\s*title\s*\:\s.*$' |awk -F ' :' '{print $2}'".mp3
done
Instead of extracting the title and renaming the file it says that the file '.mp3' already exists, would I like to rewrite it. when I type y to rewrite this new '.mp3' the same things just happens again.
I fixed the problem by putting the full path of the output file in double quotes instead of just the title extraction command
for x in *.mp3; do
ffmpeg -I $x "./$(ffmpeg -i $x 2>&1 |grep -E '^\s*title\s*\:\s.*$' |awk -F ' :' '{print $2}'
)".mp3
done
My question is why does it create a new file called .mp3 when I only wrap the title extraction command in quotes and not the whole path?
I'm sorry if this is a little lengthly, Im new to stack overflow
In the command substitution $(command), you should not wrap the command
with double quotes as $("command") especially when the command includes
options and/or pipeline sequences, because the double-quoted string is
treated as a single command.
Please see the difference of
echo $("ls")
and
echo $("ls -la")
The 1st one will work but the 2nd one does not, because bash interpretes
ls -la as a single command, not the ls command followed by -la option.
BTW if you just want to rename the files, without re-encoding (which may degrade the quality), you can
say:
for f in *.mp3; do
title=$(ffmpeg -i "$f" 2>&1 | awk '/title/ {sub(/.*title\s*:\s*/, ""); print; exit}')
mv -i -- "$f" "$title".mp3
done
The last exit is for the edge case the mp3 file includes multiple title metadata.
[Update]
As #llogan comments, the ffprobe is more robust to extract the media information
of the file. Please try instead:
for f in *.mp3; do
title=$(ffprobe -v error -of csv=p=0 -show_entries format_tags=title "$f")
mv -- "$f" "$title".mp3
done
Right now my Script looks like that:
ffmpeg -re -stream_loop -1 -i videos/fitness"$(( RANDOM % 8 ))".mp4
It searches for all videos in my folder that starts with "fitness".
fitness1.mp4
fitness2.mp4
fitness3.mp4
and so on...
and it takes 1 randomly between 1-8 ( im using /fitness"$(( RANDOM % 8 ))".mp4 )
Is there a way to just use a random mp4 file from the folder, no matter whats the name is?
Shuf
Use the shuf command:
shuf -en1 dir/*.mp4
If you don't have shuf (for instance on BSD), you can write your own shuf -en1 very easily:
shufen1() {
shift "$((RANDOM % $#))" # slightly biased towards small numbers, at most 32767
printf %s\\n "$1"
}
Pure bash solution using arrays
For completeness, here is a pure bash solution. However, this has the same problems as the self-written shufen1 function.
a=(dir/*.mp4)
printf %s\\n "${a[RANDOM % ${#a[#]}]}"
Using these solutions
Both commands work under the assumption that there is at least one mp4-file written in lowercase letters. You can use case insensitive matching using shopt -s nocaseglob.
You might want to set shopt -s failglob to get an error in case there is no such file, otherwise the literal string dir/*.mp4 will be printed.
To use any of these solutions, write them into a subshell:
ffmpeg -re -stream_loop -1 -i "$(shuf -en1 videos/*.mp4)"
ffmpeg -re -stream_loop -1 -i "$(a=(videos/*.mp4); printf %s\\n "${a[RANDOM % ${#a[#]}]}")"
find videos -type f -name '*.mp4' | shuf -n1
Find files with the name *.mp4, randomly permute the list of names and output a single filename.
ffmpeg -re -stream_loop -1 -i "$(find videos -type f -name '*.mp4' | shuf -n1)"
I am trying to make an FFMPEG script that relied on a glob input pattern from Linux to Windows. Unfortunately that is not supported so I am looking for an alternative. I do not want to have to rename or copy the files every time I run the script because the files are used elsewhere and I cannot rename them and I would like to avoid duplication or unnecessary temporary files.
Are globs numerically sequential named images my only option here? Ideally I would like to input a list of image paths to FFMPEG as a substitute for ffmpeg -i *.jpg
The workarounds are to prepare a text file with the names and use the concat demuxer.
Or you can use image2pipe
cat *.jpg | ffmpeg -f image2pipe -framerate 25 -i - out.mp4
The best solution I could find (that's Windows compatible) was to generate a line separated list of files in a text file and pass that through to FFMPEG. For example, to generate a stabilized MP4 from a bunch of JPEGs:
ffmpeg -f concat -safe 0 -i ./files.txt -vf deshake=rx=64:ry=64 ./stabilized.mp4
Where files.txt is a list of the files in the following format. The safe option toggles the ability to have absolute/relative file paths.
# this is a comment
file 'C:/path/to/file1.jpg'
file 'C:/path/to/file2.jpg'
file 'C:/path/to/file3.jpg'
I am trying to make a script to turn a bunch of timelapse images into a movie, using ffmpeg.
The latest problem is how to loop thru the images in, say, batches of 500.
There could be 100 images from the day, or there could be 5000 images.
The reason for breaking this apart is due to running out of memory.
Afterwards I would need to cat them using MP4Box to join all together...
I am entirely new to bash, but not entirely programming.
What I think needs to happen is this
1) read in the folders contents as the images may not be consecutively named
2) send ffmpeg a list of 500 at a time to process (https://trac.ffmpeg.org/wiki/Concatenate)
2b) while you're looping thru this, set a counter to determine how many loops you've done
3) use the number of loops to create the MP4Box cat command line to join them all at the end.
the basic script that works if there's only say 500 images is:
#!/bin/bash
dy=$(date '+%Y-%m-%d')
ffmpeg -framerate 24 -s hd1080 -pattern_type glob -i "/mnt/cams/Camera1/$dy/*.jpg" -vcodec libx264 -pix_fmt yuv420p Cam1-"$dy".mp4
MP4Box's cat command looks like:
MP4Box -cat Cam1-$dy7.mp4 -cat Cam1-$dy6.mp4 -cat Cam1-$dy5.mp4 -cat Cam1-$dy4.mp4 -cat Cam1-$dy3.mp4 -cat Cam1-$dy2.mp4 -cat Cam1-$dy1.mp4 "Cam1 - $dy1 to $dy7.mp4"
Needless to say help is immensely appreciated for my project
Here is something to get you started. It sorts the individual frames into time order, and then chunks them up into chunks of 500 and loops through all the chunks:
#!/bin/bash
# User-changeable number of frames per chunk
chunksize=500
# Rename files by date/time so they collate in order
jhead -n%Y-%m-%d_%H-%M-%S *.jpg
# Remove any remnants from previous runs (which may have been longer)
rm chunk* sub-*mp4
# Split filename list into chunks - chunkaa, chunkab, chunkac ...
ls *jpg | split -l $chunksize - chunk
# Put 'file' keyword before each filename
sed -i.bak 's/^/file /' chunk*
n=0
for c in chunk*; do
# Generate zero-padded output filename so that collates for final assembly too
out=$(printf "sub-%03d.mp4" $n)
echo Processing chunk $c into sequence $out
ffmpeg -f concat -i "$c" ... "$out"
((n+=1))
done
# Final assembly of "sub-*.mp4"
ffmpeg ... sub-*mp4 ...
I am trying to make an FFMPEG script that relied on a glob input pattern from Linux to Windows. Unfortunately that is not supported so I am looking for an alternative. I do not want to have to rename or copy the files every time I run the script because the files are used elsewhere and I cannot rename them and I would like to avoid duplication or unnecessary temporary files.
Are globs numerically sequential named images my only option here? Ideally I would like to input a list of image paths to FFMPEG as a substitute for ffmpeg -i *.jpg
The workarounds are to prepare a text file with the names and use the concat demuxer.
Or you can use image2pipe
cat *.jpg | ffmpeg -f image2pipe -framerate 25 -i - out.mp4
The best solution I could find (that's Windows compatible) was to generate a line separated list of files in a text file and pass that through to FFMPEG. For example, to generate a stabilized MP4 from a bunch of JPEGs:
ffmpeg -f concat -safe 0 -i ./files.txt -vf deshake=rx=64:ry=64 ./stabilized.mp4
Where files.txt is a list of the files in the following format. The safe option toggles the ability to have absolute/relative file paths.
# this is a comment
file 'C:/path/to/file1.jpg'
file 'C:/path/to/file2.jpg'
file 'C:/path/to/file3.jpg'