W pierwszej fazie po prostu wpadłem na zwykłe logiczne rozwiązanie: tworzenia symlinków. Czyli dowiązań symbolicznych jednego katalogu na inny katalog, tak w skrócie (to dla nieznających tego pojęcia, takie jakby "skróty" do plików). Najpierw napisałem sobie skrypt przechwytujący żądania generatora do utworzenia katalogu proxy, przechwycenia tego żądania i stworzenia "w locie" symlinka. Niby się sprawdzało, niby nie. Czasem generator był szybszy od skryptu i pierwszy plik proxy zapisał według swojego algorytmu, czasem udawało się przechwycić i zlinkować prawidłowo. Czyli nie tędy droga. Zresztą, co za sens przechwytywać żądania tworzenia katalogu, przecież wiem jakie będą potrzebne.
Tak więc kolejnym etapem było stworzenie po prostu z góry ustalone symlinki i katalogi docelowe, zgodne ze strukturą jakiej oczekuje DaVinci Resolve. Ostatecznie, by nie przedłużać, powstał skrypt rekursywnie przechodzący przez katalog/dysk źródłowych plików, tworzący w miejscu docelowym katalogi z nazwami bieżącego katalogu, w katalogu źródłowym tworzący symlink o nazwie "proxy", wskazujący na ten właśnie katalog. Trochę to zamotane, ale jakoś łatwiej mi nie udało się wytłumaczyć. Może po kolei jak to działa:
i tak w kółko aż do wyczerpania wszystkich katalogów. Czyli w miejscu docelowym mamy, de facto, lustrzaną kopie katalogów z dysku źródłowego, ale DaVinci Resolve lub też Blackmagic Proxy Generator je widzi jako prawidłową strukturę katalogów proxy. Niby można by stworzyć jeszcze bardziej prawidłowo na dysku/katalogu docelowym katalogi z nazwami pierwotnymi, a dopiero w nich katalogu proxy. Ale po co? To by była sztuka dla sztuki.
Trzeba zwrócić uwagę na jedną niedogodność, skrypt szuka wszędzie, tak więc w przypadku struktury XDCAM, gdzie pliki są w podkatalogu /XDROOT/Clip, zrobi symlink do katalogu Clip. To samo przy jeszcze bardziej idiotycznej strukturze BPAV, gdzie każdy z plików video ma swój osobny katalog. Najlepiej po prostu przenieść pliki video do katalogu właściwego/nadrzędnego/własnego. Nie ma sensu tutaj tłumaczyć, kto się zetknął z tymi formatami zapisu,ten wie o co chodzi.
Oto treść wydumanego w końcu, po kilku próbach, skryptu do tworzenia struktury proxy dla Resolva ( o mały włos bym nie skasował wtedy plików źródłowych, ale mam backup. Co też każdemu polecam. )
Nawet dopisałem coś w rodzaju helpa, czego normalnie nie robię, a po kilku miesiącach się zastanawiam jak też, do cholery, się tego używało :) Wystarczy skopiować zawartość textową do pliku na przykład proxyCreator.sh, zapisać gdzieś na dysku, nadać mu prawa wykonywalności i po prostu używać. Oczywiście nie jest to program z zabójczo pięknym graficznym interfejsem, ale skrypt do uruchamiania w konsoli lub w terminalu. Może nie jest doskonały, jak nic na tej planecie, ale u mnie się sprawdza bardzo dobrze. A jak ci coś nie będzie działać, to zawsze można poprawić.
Mała uwaga, jeśli by kotś chciał używać Bash, to w wersji przynajmniej 4.1. inaczej się będzie wywalać na zapisie logów. Niestety Apple, ze względu na rodzaj licencji Bash, używa nawet w najnowszysch maszynach wersji 3.2.57. Niby z powodu że bash od wersji 4.0 jest na licencji GPL v3, której to licencji Apple jakoś nie lubi. Dlaczego? Któż to wie. Jak nie wiadomo o co chodzi, pewnie chodzi o pieniądze :-) Niby trochę szkoda, ale z drugiej strony: Zsh jest bardzo kompatibilny z Bash, praktycznie wszystko działa bez problemów. A już na pewno tak proste skrypty jak ten do tworzenia katalogów proxy.
#!/bin/zsh
# --- help ---
usage() {
cat << EOF
Use: $0 [OPTION] SRC DST
Creates a directory structure on the DST, and proxy symlinks in the source directories
that point to these directories. Used to bypass BlackmagicProxyGenerator restrictions, where it is
not possible to specify the destination directory, which can be problematic in the case of a full source drive.
Thanks to this approach, you do not take up space on your source drive,
and all proxy directories point to your destination on another drive. And DaVinci Resolve works flawlessly.
--
The script is designed for Mac OSX or Linux. It may be possible to adapt it for Windows, but I am not familiar with that.
The script may contain errors. Use it at your own risk. If you notice any irregularities,
please let me know and I will correct them in my spare time. Enjoy using it, martvin.
admin @ projektika.group
OPION:
--clean Removes existing directories or proxy symlinks before creating new ones.
--exec Makes actual changes (default: dry-run mode)
--log [FILE] Saves the activity log. If FILE is not specified, a file with the date is created.
--help This page
EXAMPLE:
$0 /Volumes/SRC /Volumes/DST
$0 --exec /Volumes/SRC /Volumes/DST
$0 --clean --exec --log SRC DST
$0 --exec --log /path/to/my_proxy.log SRC DST
ATTENTION:
- Dry-run mode is the default for safety.
- Symlinks are only created if they do not exist (unless --clean is used).
- Source directories are not modified except for the proxy directory.
EOF
exit 1
}
# --- default settings ---
CLEAN=0
EXEC=0
DRY_RUN=1
LOG_FILE=""
# --- args parse ---
while [[ $# -gt 0 ]]; do
case $1 in
--clean)
CLEAN=1
shift
;;
--exec)
EXEC=1
DRY_RUN=0
shift
;;
--log)
if [[ -n "$2" && ! "$2" =~ ^--.* ]]; then
potential_path="$2"
shift 2
else
potential_path="proxy_log_$(date %Y%m%d-%H%M%S).log"
shift
fi
potential_path="${potential_path%/}"
if [[ -d "$potential_path" ]]; then
# if directory, create log file inside it
LOG_FILE="$potential_path/proxy_log_$(date %Y%m%d-%H%M%S).log"
elif [[ -n "$potential_path" ]]; then
# if file path, check if directory exists
log_dir="$(dirname "$potential_path")"
if [[ -d "$log_dir" ]]; then
LOG_FILE="$potential_path"
else
echo -e "\033[1;31mError: dir not exist: $log_dir\033[0m"
exit 1
fi
else
echo -e "\033[1;31mError: wrong path after --log\033[0m"
exit 1
fi
;;
--help)
usage
;;
*)
if [[ -z "$SRC" ]]; then
SRC="$1"
elif [[ -z "$DST" ]]; then
DST="$1"
else
echo "Error: Too many arguments."
usage
fi
shift
;;
esac
done
# --- Checking required arguments ---
if [[ -z "$SRC" || -z "$DST" ]]; then
echo "\033[1;31mError: The required SRC or DST arguments are missing, or the --log option was used without a path to the log directory.\033[0m"
usage
fi
# --- path validate ---
if [[ ! -d "$SRC" ]]; then
echo "Error: Source directory does not exist: $SRC"
exit 1
fi
# --- logs ---
LOG_FD=1 # default stdout
if [[ -n "$LOG_FILE" ]]; then
# dir exists
log_dir=$(dirname "$LOG_FILE")
if [[ ! -d "$log_dir" ]]; then
echo "Error: Log directory not exist: $log_dir"
exit 1
fi
# log file
exec {LOG_FD}>>"$LOG_FILE"
echo "=== Log start: $(date) ===" >&$LOG_FD
echo "Mode: $( [[ $DRY_RUN -eq 1 ]] && echo dry-run || echo exec )"
echo "SRC: $SRC"
echo "DST: $DST"
echo "Args: $( [[ $CLEAN -eq 1 ]] && echo '--clean ' || echo '')$( [[ $EXEC -eq 1 ]] && echo '--exec ' || echo '')$( [[ -n "$LOG_FILE" ]] && echo "--log $LOG_FILE" || echo '')"
echo "---" >&$LOG_FD
fi
# --- function ---
log_action() {
local msg="$1"
# terminal
if [[ $DRY_RUN -eq 1 ]]; then
echo "[DRY RUN] $msg"
else
echo "$msg"
fi
# file
if [[ -n "$LOG_FILE" ]]; then
local prefix=$( [[ $DRY_RUN -eq 1 ]] && echo "[DRY RUN]" || echo "[EXEC]" )
echo "$prefix $msg" >&$LOG_FD
fi
}
run() {
if [[ $DRY_RUN -eq 1 ]]; then
echo "[DRY RUN] $*" | tee -a /dev/fd/$LOG_FD 2>/dev/null
else
echo "[EXEC] $*" >&$LOG_FD
"$@"
fi
}
# --- main exec ---
if [[ $EXEC -eq 1 && ! -d "$DST" ]]; then
echo "[EXEC] Creating a target directory: $DST"
mkdir -p "$DST" || { echo "Error: Cannot create $DST"; exit 1; }
elif [[ $DRY_RUN -eq 1 && ! -d "$DST" ]]; then
log_action "Target directory required: $DST (will be created with --exec)"
fi
while IFS= read -r -d '' dir; do
# skip proxy exist dir
if [[ $(basename "$dir") == "proxy" ]]; then
continue
fi
# check for video file in dir
if find "$dir" -maxdepth 1 -type f \( \
-iname "*.mov" -o \
-iname "*.mp4" -o \
-iname "*.mxf" \
\) | grep -q .; then
rel_path="${dir#$SRC/}"
[[ "$rel_path" == "$dir" ]] && rel_path="" # ending /
target_dir="$DST/$rel_path"
proxy_path="$dir/proxy"
# 1. Create the target directory
run mkdir -p "$target_dir"
log_action "Make proxy dir: $target_dir"
# 2. Delete existing symlink/directory ONLY if --clean
if [[ $CLEAN -eq 1 ]]; then
if [[ -d "$proxy_path" || -L "$proxy_path" ]]; then
run rm -rf "$proxy_path"
log_action "The existing symlink/directory has been removed: $proxy_path"
fi
fi
# 3. Create a symlink only if not exist
if [[ -L "$proxy_path" ]]; then
current_target=$(readlink "$proxy_path" 2>/dev/null || echo "???")
if [[ "$current_target" == "$target_dir" ]]; then
log_action "Skip, symlink exitst: $proxy_path → $current_target"
else
log_action "Skip, symlink exists, but it points to: $proxy_path → $current_target"
fi
else
run ln -s "$target_dir" "$proxy_path"
log_action "Symlink sreate: $proxy_path → $target_dir"
fi
fi
done < <(find "$SRC" -type d -print0)
# --- close log ---
if [[ -n "$LOG_FILE" ]]; then
echo "=== End: $(date) ===" >&$LOG_FD
exec {LOG_FD}<&- # close file
echo "Log saved to: $LOG_FILE"
fi
echo ""
if [[ $DRY_RUN -eq 1 ]]; then
echo "Dry-run mode completed. No changes were made."
echo "Use --exec to make changes."
else
if [[ $CLEAN -eq 1 ]]; then
echo "Done. Proxies have been refreshed. (--clean)."
else
echo "Done. New proxy links have been added (incremental mode)."
fi
fi
No i w ten sposób mamy zrobione w dowolnym miejscu katalogi, w których będą generowane pliki proxy. To w przypadku korzystania z Blackmagic Proxy Generator. No bo jak wcześniej zostało powiedziane, sam Resolve DaVinci ma możliwość wskazania, gdzie mają być. Dodatkowo, kiedy potrzebujemy przenieść pliki proxy, na przykład na laptop, żeby podczas urlopu nie marnować czasu na plaży :-), to po prostu kopiujemy katalog i mamy odwzorowanie originalu. Nie trzeba robić jakieś extract proxy czy temu podobne.
Polecam używanie opcji --log. Można dać ścieżkę z własną nazwą pliku albo wskazać tylko katalog docelowy dla logu. Czyli jeżeli użyjemy składni proxyCreator.sh --log DestinationDir SourceDir DestinationDir otrzymamy logi w katalogu docelowym, nazwane "proxy_log_Aktualna-Data-Godzina-Minuta.log". Po paru tygodniach lub miesiącach może się okazać, że te informacje są całkiem przydatne. A przy uruchomieniu bez opcji --exec, otrzymujemy symulację przyszłych działań, więc możemy prześledzić, co będzie robione. To akurat przy dużej liczbie katalogów proxy może sie przydać, bo może się okazać, że część katalogów ma wygenerowane swoje symlinki
Ale ponieważ jak się powiedziało A, to należało by powiedzieć B, to dalszym krokiem było stworzenie własnego generatora właściwych plików proxy video, z dowolnym kodekiem, wielkością czy bitrate. Czyli technologiczny wyścig z firmą Blackmagic :-)
Tak więc jedziemy dalej z koksem. Mamy już prześliczną strukturę katalogów proxy, czas do niej zapakować jakieś pliki. Najprostszym rozwiązaniem, do obróbki wszelakiej plików video z konsoli, jest FFMPEG. Taki niepozorny programik, ale potrafi w zasadzie wszystko. No może poza gotowaniem i sprzątaniem w domu. Ma wiele odmian i modyfikacji, ale osobiście korzystam po prostu z klasycznej instalacji poprzez brew install ffmpeg. Może inne wersje mają czasami większe możliwości, ale potrzebuje prostych rozwiązań. Owszem, na przykład FFMBC jest wręcz stworzony dla profesjonalnej obróbki video, ale ja tworzę tylko proxy :-)
Wróćmy do generowania plików. Tym razem zrezygnowałem z "Zsh" na rzecz zwykłego "Bash". Najzwyczajniej w świecie, miałem już prostą wersję skryptu z lat dawnych, i nie zamierzałem przepisywać na niby kompatybilny, ale jednak nie do końca format. Szczegóły nie są istotne, ale przekazywanie argumentów jako tablic albo jako stringów jednak robi różnicę. Albo się po prostu na tym nie znam. Nie wnikajmy, zresztą nie zamierzam również toczyć "flame war" na temat co jest lepsze. Po prostu czasem coś jest przydatne lub gotowe, a czasami coś innego.
Tak więc na bazie starego, kilku linijkowego skryptu, powstało coś w rodzaju "kombajnu" do plików proxy. Pierwotnie był pomysł, by połączyć proxyDirCreator i proxyFileGenerator (takie dumne nazwy nadałem plikom :-), ale to by było totalnie bez sensu. Nie każdy potrzebuje symlinkować katalogi proxy na inny dysk.
Czyli jest tak, skrypt przechodzi rekursywnie po katalogu źródłowym, wyszukuje pliki .mov, .mp4, .mxf (z takimi się najczęściej stykam w pracy), jeżeli nie istnieje, to tworzy w danym poziomie katalog proxy (jeśli istnieje to korzysta z istniejącego, czy to katalog czy symlink), kompresuje wszystkie pliki z danego katalogu, dopisuje wynik do pliku ".csv" jako swoisty log, i kontynuuje swoją podróż po katalogach. I tak sobie wędruje, aż nie przerobi wszystkie pliky z danej lokacji.
Ma kilka opcji, wszystko jest umieszczone w kodzie skryptu, więc nie ma sensu tu omawiać. W każdym razie, tak jak w proxyDirCreator, domyślnie nic nie tworzy, tylko pokazuje co by chciał zrobić. Zawsze lepiej zobaczyć wcześniej, jakiego rodzaju zmiany ma zamiar jakiś program zrobić, niż potem płakać, że coś poszło nie tak.
Jeśli chcesz generować kilka plików naraz, trzeba doinstalować GNU Parallel. Szczerze to nie wiem, z kilku rdzeni korzysta FFMpeg podcza pracy, mam wrażenie że tylko z jednego. Tak więc pomnożenie liczby procesów może znacząco skrócić czas generowania, i do tego można na pozostałych rdzeniach spokojnie pracować. Ale to już temat na osobny artykuł. W lażdym razie, u mnie to działa, i nawet dość zasuwa. Z tym, że korzystam z procesora Apple M4, który sam w sobie jest dość szybki. I nie miałem jak przeprowadzić testów na innych maszynach.
#!/bin/bash
# --- help ---
usage() {
cat << EOF
Usage: $0 [quality] [--jobs N] [--overwrite] [--exec]
Example: $0 /Users/user/Projekt medium # simulation mode, default
$0 /Users/user/Projekt medium --exec # single mode
$0 /Users/user/Projekt medium --jobs 4 --overwrite --exec # parallel mode, overwrite old proxy
Generates proxy files with the selected quality setting. Works recursively, skips "proxy" directories.
---
The script is designed for Mac OSX or Linux. It may be possible to adapt it for Windows, but I am not familiar with that.
The script may contain errors. Use it at your own risk. If you notice any irregularities,
please let me know and I will correct them in my spare time. Enjoy using it :-)
martvin
admin @ projektika.group
---
ARGUMENTS:
start_path Directory to start searching from
quality Quality profile (default: medium, see options below)
light - low bitrate, fast, half resolution (H.264)
medium - speed/quality balance, half resolution (H.264)
high - full resolution, high quality (H.264)
h265-half - half resolution, excellent compression (H.265/HEVC)
h265-full - full resolution, excellent compression (H.265/HEVC)
prores - ProRes 422 (10-bit), half resolution
prores-lt - ProRes LT (lower bitrate), half resolution
prores-proxy - ProRes Proxy (dedicated proxy codec), half resolution
prores-proxy-full - ProRes Proxy, full resolution
OPTIONS:
--jobs N Number of parallel processes (default: 1), use with GNU parallel
--overwrite Overwrite existing proxy files
--exec Execute (without this flag runs in dry-run mode)
--dry-run Shows what it would do without generating (default)
EOF
exit 1
}
if [ $# -eq 0 ]; then
echo "Please specify the start path"
usage
fi
# --- dependency check ---
check_dependencies() {
if ! command -v ffmpeg &> /dev/null; then
echo "\033[1;31mERROR: ffmpeg is not installed or not in PATH.\033[0m"
echo "\033[1;31minstall: brew install ffmpeg\033[0m"
exit 1
fi
if [[ $jobs -gt 1 ]] && ! command -v parallel &> /dev/null; then
echo "\033[1;31mWARNING: GNU parallel is not installed - working sequentially.\033[0m"
jobs=1
fi
}
start_path="$1"
quality="${2:-medium}"
jobs=1
overwrite=0
DRY_RUN=1
# Parsing additional arguments
shift 2 # skip after start_path & quality
while [[ $# -gt 0 ]]; do
case "$1" in
--jobs)
if [[ -n "$2" && "$2" =~ ^[0-9] $ ]]; then
jobs="$2"
shift 2
else
echo "\033[1;31mERROR: --jobs needs a number.\033[0m"
usage
fi
;;
--overwrite)
overwrite=1
shift
;;
--exec)
DRY_RUN=0
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--help)
usage
;;
*)
echo "Unknown option: $1, maybe a typo? Use --help for help."
shift
;;
esac
done
# Validate start path
if [ ! -d "$start_path" ]; then
echo "\033[1;31mERROR: Directory '$start_path' does not exist\033[0m"
exit 1
fi
case "$quality" in
light)
scale="scale=iw/2:ih/2"
codec="-c:v libx264 -preset ultrafast -crf 28 -pix_fmt yuv420p -c:a aac -b:a 128k"
ext="mp4"
;;
medium)
scale="scale=iw/2:ih/2"
codec="-c:v libx264 -preset fast -crf 23 -pix_fmt yuv420p -c:a aac -b:a 192k"
ext="mp4"
;;
high)
scale="scale=iw:ih"
codec="-c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p -c:a aac -b:a 256k"
ext="mp4"
;;
h265-half)
#compatible setting for macosx quicktime player - -profile:v main -level 4.0 -tag:v hvc1
scale="scale=iw/2:ih/2"
codec="-c:v libx265 -preset medium -crf 26 -pix_fmt yuv420p -profile:v main -level 4.0 -tag:v hvc1 -c:a aac -b:a 192k"
ext="mp4"
;;
h265-full)
#compatible setting for macosx quicktime player
scale="scale=iw:ih"
codec="-c:v libx265 -preset medium -crf 23 -pix_fmt yuv420p -profile:v main -level 4.0 -tag:v hvc1 -c:a aac -b:a 256k"
ext="mp4"
;;
prores)
#prores halfres normal codec
scale="scale=iw/2:ih/2"
codec="-c:v prores_ks -profile:v 2 -pix_fmt yuv422p10le -vendor apl0 -bits_per_mb 2000 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -c:a pcm_s16le"
ext="mov"
;;
prores-lt)
scale="scale=iw/2:ih/2"
codec="-c:v prores_ks -profile:v 1 -pix_fmt yuv422p10le -vendor apl0 -bits_per_mb 3500 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -c:a pcm_s16le"
ext="mov"
;;
prores-proxy)
scale="scale=iw/2:ih/2"
codec="-c:v prores_ks -profile:v 0 -pix_fmt yuv422p10le -vendor apl0 -bits_per_mb 2000 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -c:a pcm_s16le"
ext="mov"
;;
prores-proxy-full)
scale="scale=iw:ih"
codec="-c:v prores_ks -profile:v 0 -pix_fmt yuv422p10le -vendor apl0 -bits_per_mb 2000 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -c:a pcm_s16le"
ext="mov"
;;
*)
echo "Error in quality settings: $quality"
echo "Available options: light|medium|high|h265-half|h265-full|prores|prores-lt|prores-proxy|prores-proxy-full"
exit 1
;;
esac
# Generate CSV filename with timestamp and quality
timestamp=$(date "%Y%m%d_%H%M%S")
csv_file="$start_path/proxy_log_${timestamp}_${quality}.csv"
# old maps, when DJI file, generate bad TC. Simply put, these "idiots" do not adhere to any standards.
# maps="-map 0:v:0 -map 0:a? -map_metadata 0 -copytb 1"
#new maps, i hope is ok :-)
maps="-map 0:v:0 -map 0:a? -map_metadata 0"
check_dependencies
# Create CSV header
echo -e "origin_file\tproxy_file\tcreation_time\tduration" > "$csv_file"
create_proxy() {
local file="$1"
local dir_name=$(dirname "$file")
local basename=$(basename "$file")
local filename="${basename%.*}"
local output_dir="$dir_name/Proxy"
local output_file="$output_dir/$filename.$ext"
# Check if ffmpeg is available
local ffPath=$(command -v ffmpeg)
if [ -z "$ffPath" ]; then
echo "ERROR: ffmpeg not found in PATH"
return 1
fi
mkdir -p "$output_dir"
# Check if a file with the same name but different extension already exists
local mp4_file="$output_dir/$filename.mp4"
local mov_file="$output_dir/$filename.mov"
# Check file existence if not overwrite
if [ "$overwrite" -eq 0 ]; then
if [ -f "$mp4_file" ] || [ -f "$mov_file" ]; then
echo "File exists (as .mp4 or .mov), skipping: $output_file"
return 0
fi
fi
# Set ffmpeg overwrite flag
local ffmpeg_flag=""
if [ "$overwrite" -eq 1 ]; then
ffmpeg_flag="-y"
else
ffmpeg_flag="-n"
fi
# check if attached pic
attached_id=$(ffmpeg -hide_banner -i "$file" 2>&1 | grep "Video.*attached pic" | sed -E 's/.*Stream #0:([0-9] )\[.*:([0-9] )\].*/\2/' | head -n1)
# new maps
if [ -n "$attached_id" ]; then
echo "Attached picture detected on stream #$attached_id — excluding it..."
map_fix="$maps -map -0:v:$attached_id"
else
map_fix="$maps"
fi
if [ $DRY_RUN -eq 1 ]; then
echo "[DRY RUN] Would run: $ffPath -nostdin $ffmpeg_flag -i \"$file\" $map_fix -vf \"$scale\" $codec \"$output_file\""
return 0
fi
# Execute ffmpeg
echo "Processing: $file → $output_file"
if ! < /dev/null $ffPath -nostdin $ffmpeg_flag -i "$file" $map_fix \
-vf "$scale" \
$codec \
"$output_file" 2> >(tee "/tmp/ffmpeg_error.log" >&2); then
echo "ERROR: Failed to process $file"
echo " See detailed log: /tmp/ffmpeg_error.log"
tail -n 20 /tmp/ffmpeg_error.log
echo ""
return 1
fi
# Extract metadata
local ffmpeg_metadata=$($ffPath -i "$file" 2>&1)
local duration=$(echo "$ffmpeg_metadata" | grep "Duration:" | head -n 1 | awk '{print $2}' | sed 's/,//')
local creation_time=$(echo "$ffmpeg_metadata" | grep -i "creation_time" | head -n 1 | awk '{print $3" "$4}')
# Write to CSV
echo -e "$file\t$output_file\t$creation_time\t$duration" >> "$csv_file"
echo "Finished: $file → $output_file"
return 0
}
# Export function and variables for parallel processing
export -f create_proxy
export scale codec ext csv_file overwrite DRY_RUN maps
# Display settings
echo "=== Proxy Generator Settings ==="
echo "Start path: $start_path"
echo "Quality: $quality"
echo "Jobs: $jobs"
echo "Overwrite: $([ $overwrite -eq 1 ] && echo "yes" || echo "no")"
echo "Mode: $([ $DRY_RUN -eq 1 ] && echo "DRY RUN" || echo "EXECUTION")"
echo "CSV output: $csv_file"
echo "================================"
# Main processing
if [ "$jobs" -gt 1 ]; then
if command -v parallel > /dev/null 2>&1; then
echo "Starting parallel processing with $jobs jobs..."
find "$start_path" -type f \( -iname "*.mxf" -o -iname "*.mp4" -o -iname "*.mov" \) \
-not -path "*/proxy" -not -path "*/proxy/*" -print0 | \
parallel -0 -j"$jobs" create_proxy
else
echo "WARNING: GNU parallel is not installed. Falling back to sequential mode..."
jobs=1
fi
fi
if [ "$jobs" -eq 1 ]; then
echo "Starting sequential processing..."
while IFS= read -r -d '' file; do
create_proxy "$file"
done < <(find "$start_path" -type f \( -iname "*.mxf" -o -iname "*.mp4" -o -iname "*.mov" \) \
-not -path "*/Proxy" -not -path "*/Proxy/*" -print0)
fi
echo "Processing completed. Results saved to: $csv_file" Oczywiście, można rozbudować skrypt. Przydałaby się na przykład możliwość po prostu generowania proxy z A do B, bez tworzenia symlinków czy katalogów. Ale chciałem po prostu symulować działanie oryginalnego programu. Możliwości jest nieskończona ilość, co użytkownik, to zapewne inne potrzeby. No cóż, nie ma rzeczy doskonałych :-)
Druga rzecz do wyjaśnienia, argumenty w jednym skrypcie są na początku w innym na końcu polecenia. No cóż, po prostu tak zostało z poprzedniej wersji. Nawet mi nie przyszło do głowy to poprawiać, aby było tak samo. Ale znależli się tacy co zauważyli :-) Dzieki za uwagi, ale to jest zasadniczo żadna przeszkoda w używaniu. A na dodatek każda zmiana nam trochę pomaga przełamywać rutynę, czyli nasz mózg pracuje wydajniej :-)
Po krótkiej przygodzie z ComfyUI, (niestety przez własną nieuwagę uruchomiłem w nieizolowanym środowisku), wykasowało mi ffmpeg. No nic, ufając konsoli, uruchamiam "brew instal ...", a tu jakaś zależność ma nie taki certyfikat SSL, lub dziabeł wie co poszło nie tak. Po krótkim zastanowieniu doszedłem do wniosku, że owszem, brew jest wygodne, ale w zasadzie mi nie jest potrzebne. Potrzebuje tylko FFMpeg i FFProbe. A ponieważ na stronie evermeet.cx są dostępne gotowe binarki (w moim przypadku na mac osx) dla obydwóch tych programów, najłatwiej będzie po prostu użyć gotowca, zamiast budować całe drzewo zależności. Może to trochę zamotane, jak wszystkie moje opisy, ale po prostu nie potrzebuje zaawansowanych kodeków do zwykłego tworzenia proxy. A te które są zawarte w binarnych wersjach FFMpeg w zupełności wystarczają.
Nie przedłużajmy, oto gotowy skrypt instalujący FFMpeg i FFProbe w najnowszej dostępnej wersji, bez Homebrew, kompilacji i innych dziwnych rzeczy :-)
#!/bin/bash
set -e
# install binary without Homebrew
install_binary() {
local name=$1
local tmpfile="/tmp/${name}.zip"
local url="https://evermeet.cx/ffmpeg/getrelease/${name}/zip"
echo "|-> Get ${name}..."
curl -L "$url" -o "$tmpfile" || {
echo -e "\033[0;31m Failed to download ${name}.\033[0m"
return 1
}
echo "Unpack ${name}..."
unzip -o "$tmpfile" -d /tmp/
echo "|-> Install ${name} to /usr/local/bin..."
sudo mv "/tmp/${name}" "/usr/local/bin/${name}"
sudo chmod x "/usr/local/bin/${name}"
echo "|-> Clear... "
rm -f "$tmpfile"
echo "|-> ${name} updated!"
"/usr/local/bin/${name}" -version | head -n 1
echo
}
echo "==============================="
echo "Install/update FFmpeg & FFprobe"
echo "==============================="
install_binary ffmpeg
install_binary ffprobe
echo -e "\033[0;32m All OK! \033[0m"
Po prostu skopiuj, wklej do pliku, nadaj uprawnienia wykonywania i uruchom w konsoli. Pewnie zapyta o hasło przed instalacją FFMpega, ale różnie to bywa. I jak się to mówi - "U mnie wszystko działa" :-)
Dodano usuwanie ścieżki z "attached pic", powodowalo to czasem dziwne zachowania, szczególnie z plikami DJI. I w tych samych plikach większy problem był z timecodami. Osmo DJI niby ma ścieżkę TC, ale oznaczoną jako "codec:none". Drony DJI w ogóle nie mają TC, najwyżej timestamp w nazwie lub nagłówku. Teoretycznie opcja "-map 0:d?" powinna ignorować brak ścieżki, ale nie do końca to działa.
Właśnie chociażby w przypadku oznaczenia `codec:none`, co jet totalną pomyłką według mnie. No ale to prywatne zdanie, pewnie to ma jakiś ukryty przed światem sens.
W każdym razie, jeśli koniecznie potrzebujesz ścieżkę TC, a ogolnie praktycznie zawsze timecode jest również zapisywany jako metadata, to dodaj "-map 0:d?" do deklaracji maps. I tyle. Można by rozbudować, dodać wyjątki, jakieś if i else. Ale uniwersalność ma swoje granice. Moja cierpliwość też :-)
Tym razem niestety troche mojej nieuwagi. Ale pokrótce, poprawione kodeki Prores, coś mi się namieszało i proxy generowało mi 4444. Przeoczenie, albo zamroczenie :-) I druga sprawa, poprawione "-maps" dla lepszej obróbki kodeków DJI, które nie trzymają żadnych normalnych standardów. Ale to temat chyba na całą książkę. Po kilku testach wydaję mi się, iż działa wszystko poprawnie. Wprawdzie jeszcze chciałem dorzucić kilka kompatybilności, tyle że brak czasu. Jeśli coś by ci nie działało, daj znać.