Last active
October 10, 2024 09:11
-
-
Save thediveo/57fd76e4d15252232aaacc7e422a79a2 to your computer and use it in GitHub Desktop.
Scans for extracted frame image PNG files and (re)extracts the frame directly from their corresponding video media files, using source resolution and orientation instead of project resolution and orientation. Frame image PNG files are searched in the current working directory, as well as all its sub directories.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# (Re-) Extract frame images directly from its corresponding video media | |
# file. The frame image filenames are expected to be in the format of | |
# videofilename-f000000.png, that is: the filename of the video media file, | |
# but without its extension, then followed by "-f" and a six digit frame | |
# number (base 10), and finally the ".png" suffix. | |
# | |
# Copyright 2018 TheDiveO | |
# | |
# https://gist.github.com/TheDiveO/57fd76e4d15252232aaacc7e422a79a2 | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a | |
# copy of this software and associated documentation files (the "Software"), | |
# to deal in the Software without restriction, including without limitation | |
# the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
# and/or sell copies of the Software, and to permit persons to whom the | |
# Software is furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
# | |
shopt -s extglob | |
shopt -s globstar # who comes up with such option identifiers? | |
function version { | |
echo "version 1.0.0" | |
exit 1 | |
} | |
function usage { | |
echo "Usage:" $(basename "$0") "[options] project.kdenlive" | |
echo "Scans for extracted frame image PNG files and (re)extracts the frame directly" | |
echo "from their corresponding video media files, using source resolution and orientation" | |
echo "instead of project resolution and orientation. Frame image PNG files are searched" | |
echo "in the current working directory, as well as all its sub directories." | |
echo "" | |
echo "Options:" | |
echo " -h, --help print help and exit" | |
echo " --version print version and exit" | |
exit 1 | |
} | |
projectfile="" | |
# First some not too complicated command line argument parsing... | |
while [[ $# -gt 0 ]]; do | |
case "$1" in | |
-h|--h) | |
usage | |
;; | |
--version) | |
version | |
;; | |
*) | |
if [[ ! -z "$projectfile" ]]; then | |
echo "error: too many arguments" | |
exit 1 | |
fi | |
projectfile="$1" | |
;; | |
esac | |
shift | |
done | |
# Some basic checks on the required first parameter, the Kdenlive project | |
# filename: it must be specified, AND must end in ".kdenlive", AND must | |
# refer to an existing and non-empty file. | |
if [[ -z "$projectfile" ]]; then | |
usage | |
fi | |
if [[ ! "$projectfile" =~ .*\.kdenlive ]]; then | |
echo "invalid argument: Kdenlive project filename must end in .kdenlive" | |
exit 1 | |
fi | |
if [[ ! -s "$projectfile" ]]; then | |
echo "invalid argument: '$1' is not a file" | |
exit 1 | |
fi | |
# Fetch the project frame rate from the project's .kdenlive file: it is | |
# stored in Kdenlive project files not as a floating point value but | |
# instead as numerator and denominator. | |
fps_num=$(xmllint --xpath "string(/mlt/profile/@frame_rate_num)" "$projectfile") | |
fps_den=$(xmllint --xpath "string(/mlt/profile/@frame_rate_den)" "$projectfile") | |
# Just for the reporting eye candy we go through hops and loops to achieve | |
# visually appealing framerate number formatting :) Thanks to | |
# https://stackoverflow.com/a/30048933/6632214 for the sed expression to | |
# remove trailing 0 decimal places. | |
dispfps=$(echo "scale=4; ($fps_num/$fps_den+0.00005)/1" | bc \ | |
| sed "/\./ s/\.\{0,1\}0\{1,\}$//") | |
echo "detected project framerate:" $dispfps "fps" | |
# Work on all extracted frame image files following the filename convention | |
# of <video filename>-f<6digit frame number>.png | |
# Since the video filename included in the frame image filename lacks the | |
# original extension, we currently try a (small) set of well-known extensions, | |
# both in lowercase and uppcase. | |
for framepng in **/*-f+([0-9]).png; do | |
if [[ "$framepng" =~ (.*)-f([[:digit:]]+)\.png ]]; then | |
fbasename=${BASH_REMATCH[1]} | |
frameno=${BASH_REMATCH[2]} | |
for ext in mp4 MP4; do | |
if [[ -f "$fbasename.$ext" ]]; then | |
videofile=$fbasename.$ext | |
break | |
fi | |
done | |
if [[ -f "$videofile" ]]; then | |
# WARNING: we cannot directly use the frame number derived from the frame | |
# image filename, because Kdenlive calculates those framenumbers based on | |
# the project framerate, yet ffmeg uses frame numbers based on the particular | |
# video file framerate (yeah, this is simplified, I know). In consequence, we have | |
# to convert the project fps-based frame number into a fps-invariant time duration | |
# which we can then use with the "-ss" parameter. The time duration can be | |
# either specified as hh:mm:ss.d..., or simply as s+.d... (that is, seconds | |
# and decimal places). Using the simple seconds.decimals format relieves | |
# us from complex drop frame calculations in case of "weird" project | |
# frame rates. | |
# | |
# References: | |
# https://ffmpeg.org/ffmpeg.html#toc-Main-options | |
# https://ffmpeg.org/ffmpeg-utils.html#time-duration-syntax | |
# First remove all leading zeros from the filename-embedded frame | |
# number and interpret it as base 10. | |
let "fno=10#$frameno" | |
secs=$(echo "scale=3; ($fno*$fps_den)/$fps_num" | bc) | |
echo "frame #$frameno@${dispfps}fps/$secs from '$videofile'" | |
ffmpeg -loglevel warning -y -ss $secs -i "$videofile" -vframes 1 "$framepng" | |
if [[ $? -ne 0 ]]; then | |
tput setaf 1; echo "frame extraction failed, exiting."; tput sgr0 | |
exit 1 | |
fi | |
tput setaf 2; echo "==> '$framepng'"; tput sgr0 | |
else | |
tput setaf 5; echo "Skipping non-existing '$videofile'"; tput sgr0 | |
fi | |
fi | |
done |
Your file name looks fine to me. It appears to be a ffmpeg-originating error message, indicating that there is some change in the ffmpeg args. Unfortunately, I haven't used the script for a long time so I don't know what ffmpeg now expects.
it reads to me like an error generated by your script, but that may be immaterial if you have moved on from kdenlive to something better? i like kdenlive, but i've had a constant stream of niggles, and the crazy idea to not extract a frame from the image in the preview window seems to me just daft. i glanced at olive, but can't find a decent "how to" manual.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
more a question than a comment:
just trying to extract a single frame:
what is the correct syntax? sorry i couldn't figure it out from reading the code