MPV:Example:TrimRange
mpv 와 ffmpeg를 사용하여 스샷(s key) 2장의 구간을 비디오로 잘라내는 bash 샘플
Bash script
#!/usr/bin/env bash
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" || exit; pwd)
USAGE="
Usage: ${BASH_SOURCE[0]} [options] video
Available options are:
-h, --help Print this message.
-i {sec} Specify update interval. (seconds)
-t {dir} Specify a temporary directory.
-v, --verbose Be more verbose/talkative during the operation.
-- Stop handling options.
"
OPEN_FILE_PY_CODE="
from tkinter import filedialog as fd
print(fd.askopenfilename())
"
TEMP_DIR="$ROOT_DIR/temp"
VERBOSE=0
INTERVAL_SECONDS=1
INPUT_PATH=
MPV_PID=
function print_error
{
# shellcheck disable=SC2145
echo -e "\033[31m$@\033[0m" 1>&2
}
function print_message
{
# shellcheck disable=SC2145
echo -e "\033[32m$@\033[0m"
}
function print_verbose
{
if [[ $VERBOSE -eq 1 ]]; then
echo "$@"
fi
}
trap 'cancel_black' INT
function cancel_black
{
print_error "An interrupt signal was detected."
if [[ -n $MPV_PID ]]; then
kill "$MPV_PID"
fi
exit 1
}
function print_usage
{
echo "$USAGE"
}
if ! command -v mpv &> /dev/null; then
print_error "Not found mpv executable"
exit 1
fi
if ! command -v ffmpeg &> /dev/null; then
print_error "Not found ffmpeg executable"
exit 1
fi
while [[ -n $1 ]]; do
case $1 in
-h|--help)
print_usage
exit 0
;;
-v|--verbose)
VERBOSE=1
shift
;;
-i)
INTERVAL_SECONDS=$2
shift 2
;;
-t)
TEMP_DIR=$2
shift 2
;;
--)
shift
break
;;
-*)
print_error "Unknown option: $1"
exit 1
;;
*)
INPUT_PATH="$1"
break
;;
esac
done
if [[ -z $INPUT_PATH ]]; then
INPUT_PATH=$(python -c "$OPEN_FILE_PY_CODE")
fi
if [[ ! -f "$INPUT_PATH" ]]; then
print_error "Input file is required"
exit 1
fi
if [[ -n $(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type f -a -name '*.jpg') ]]; then
print_error "An image file exists in the temporary directory."
print_error "Please remove all image files before proceeding."
exit 1
fi
INPUT_PARENT="${INPUT_PATH%/*}"
INPUT_FILENAME="${INPUT_PATH##*/}"
INPUT_NAME=${INPUT_FILENAME%.*}
INPUT_EXT=${INPUT_FILENAME#*.}
mpv \
--screenshot-template=%P \
--screenshot-directory="$TEMP_DIR" \
--screenshot-format=jpg \
--screenshot-jpeg-quality=0 \
"$INPUT_PATH" &> /dev/null &
MPV_PID=$!
echo "'mpv' process ID is $MPV_PID"
echo "To exit, press Ctrl+C"
function do_update
{
print_verbose "Next update $(date "+%Y-%m-%d %H:%M:%S") ..."
local files=()
mapfile -t files < <(find "$TEMP_DIR" -mindepth 1 -maxdepth 1 -type f -a -name '*.jpg' | sort)
local files_count="${#files[*]}"
if [[ $files_count -lt 2 ]]; then
return 0
fi
local file0="${files[0]}"
local file1="${files[1]}"
local filename0="${file0##*/}"
local filename1="${file1##*/}"
local name0=${filename0%.*}
local name1=${filename1%.*}
local suffix0=${name0//:/}
local suffix1=${name1//:/}
local output="${INPUT_PARENT}/${INPUT_NAME}-${suffix0}_${suffix1}.${INPUT_EXT}"
print_message "Trim video: $name0 ~ $name1"
ffmpeg \
-hide_banner \
-loglevel error \
-i "$INPUT_PATH" \
-ss "$name0" \
-to "$name1" \
-c:v copy \
"$output"
rm -v "$file0" "$file1"
}
while true; do
do_update
if ! ps --pid $MPV_PID &> /dev/null; then
echo "'mpv' process has terminated."
exit 0
fi
sleep "${INTERVAL_SECONDS}s" &> /dev/null
done
Windows Git-Bash 에서 작동하는 샘플
우선 다음 프로그램들을 설치하자:
- git-bash - Windows 용 Git 을 설치하면 된다.
- python - MS Store 에서 설치 가능.
- mpv - MS Store 에서 설치 가능. (Unofficial)
- ffmpeg - FFmpeg 홈페이지에서 다운로드 가능.
위 패키지들은 choco, scoop, winget, 직접 설치 중 하나의 방식으로 설치하자.
만약 winget을 사용한다면 다음 명령으로 전부 설치 가능:
winget install --id Git.Git -e --source winget
winget install python
winget install mpv
winget install ffmpeg
다음 스크립트를 추가하자:
#!/usr/bin/env bash
if ! command -v mpv &> /dev/null; then
echo "Not found mpv executable"
exit 1
fi
if ! command -v ffmpeg &> /dev/null; then
echo "Not found ffmpeg executable"
exit 1
fi
USAGE="
Usage: ${BASH_SOURCE[0]} [options] video
Available options are:
--interval={sec} Specify update interval. (seconds)
-i {sec} Same '--interval' flag.
--temp={dir} Specify a temporary directory.
-t {dir} Same '--temp' flag.
--extension={ext} Specify a output extension.
-e {ext} Same '--temp' flag.
-h, --help Print this message.
-- Stop handling options.
"
SELECT_FILENAME_PY_CODE="
from tkinter import filedialog
print(filedialog.askopenfilename())
"
MPV_PID=
trap 'on_interrupt_trap' INT
function on_interrupt_trap
{
echo "An interrupt signal was detected."
if [[ -n $MPV_PID ]]; then
echo -v "Send a KILL signal to the mpv ($MPV_PID)"
kill "$MPV_PID"
fi
exit 1
}
function print_usage
{
echo "$USAGE"
}
function mpv_cut_main
{
local temp_dir=
local interval_seconds=1
local input_path=
local output_ext=
while [[ -n $1 ]]; do
case $1 in
--interval=*)
interval_seconds=${1:11}
shift
;;
-i|--interval)
interval_seconds=$2
shift 2
;;
--temp=*)
temp_dir=${1:7}
shift
;;
-t|--temp)
temp_dir=$2
shift 2
;;
--extension=*)
output_ext=${1:12}
shift
;;
-e|--extension)
output_ext=$2
shift 2
;;
-h|--help)
print_usage
exit 0
;;
--)
shift
break
;;
-*)
echo -s "Invalid flag: $1"
return 1
;;
*)
input_path="$1"
break
;;
esac
done
if [[ -z $input_path ]]; then
input_path=$(python -c "$SELECT_FILENAME_PY_CODE")
fi
if [[ ! -f "$input_path" ]]; then
echo "Not found input file."
exit 1
fi
local input_parent="${input_path%/*}"
local input_filename="${input_path##*/}"
local input_name=${input_filename%.*}
local input_ext=${input_filename#*.}
if [[ -z $output_ext ]]; then
output_ext=$input_ext
fi
if [[ -z $temp_dir ]]; then
temp_dir="${input_path}.temp"
fi
if [[ ! -d "$temp_dir" ]]; then
mkdir -vp "$temp_dir"
fi
if [[ -n $(find "$temp_dir" -mindepth 1 -maxdepth 1 -type f -a -name '*.jpg') ]]; then
echo "An image file exists in the temporary directory."
echo "Please remove all image files before proceeding."
exit 1
fi
mpv \
--screenshot-template=%P \
--screenshot-directory="$temp_dir" \
--screenshot-format=jpg \
--screenshot-jpeg-quality=0 \
"$input_path" &> /dev/null &
MPV_PID=$!
echo "'mpv' process ID is $MPV_PID"
echo "To exit, press Q in mpv ..."
local files
declare -a files
while true; do
# echo "Next iter $(date "+%Y-%m-%d %H:%M:%S") ..."
mapfile -t files < <(
find "$temp_dir" -mindepth 1 -maxdepth 1 -type f -a -name '*.jpg' | sort
)
local files_count="${#files[*]}"
if [[ $files_count -ge 2 ]]; then
local file0="${files[0]}"
local file1="${files[1]}"
local filename0="${file0##*/}"
local filename1="${file1##*/}"
local begin_text=${filename0%.*}
local end_text=${filename1%.*}
local begin=$(echo $begin_text | sed -e 's/_/:/g')
local end=$(echo $end_text | sed -e 's/_/:/g')
local begin_short=$(echo $begin_text | sed -e 's/_//g')
local end_short=$(echo $end_text | sed -e 's/_//g')
local suffix="${begin_short}_${end_short}"
local output="${input_name}-${suffix}.${output_ext}"
echo "Input file: $input_path"
echo "Output file: $output"
echo "Cutting video playback section in progress ($begin ~ $end) ..."
ffmpeg \
-hide_banner \
-loglevel error \
-i "$input_path" \
-ss "$begin" \
-to "$end" \
-c copy \
"$output"
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
echo "Video cutting was successful: $output"
else
echo "Failed to cut video: $exit_code"
fi
rm -v "$file0" "$file1"
fi
if ! ps -p $MPV_PID &> /dev/null; then
echo "'mpv' process has terminated."
exit 0
fi
sleep "${interval_seconds}s" &> /dev/null
done
}
mpv_cut_main "$@"
주의할 점
mpv의 스냅샷 파일이 Windows 와 Linux 계열과 다르다. 스크립트 작성시 주의하자.