studio graficzne, wstega Mobiusa, Projektika Group

Generowanie proxy dla DaVinci Resolve, gdziekolwiek

omijanie ograniczeń Blackmagic Proxy Generator

Blackmagic Proxy Generator

Świetny program, dodatek do Resolva, umożliwiający tworzenie plików proxy w tle, bez angażowania głównego programu. Jednak jego dość sporym ograniczeniem jest sztywna zasada tworzenia katalogów proxy. Zawsze są umieszczone w katalogu zawierającym pliki video. Czyli zawsze mamy strukturę typu:
/rootDir
  -> /videoDir
    -> /Proxy
Co faktycznie jest dość logiczne,  ale życie ma swoje ciemne strony. Po pierwsze możemy nie chcieć mieć proxy na tym dysku, po drugie, najzwyczajniej w świecie nie ma na nim miejsca.  Tak też było w moim przypadku. Surówka, czyli materiały źródłowe, były na zewnętrznym dysku ssd, dość dużym, bo 2TB, ale napchany po brzegi. Zostało może jakieś 50MB. Około 1400 plików do filmu przyrodniczego.  W kilkunastu różnych formatach i rozdzielczościach. Niby nic nadzwyczajnego, pochodziły z różnych kamer, fotoaparatów, niektóre z telefonów. A ponieważ nie miałem możliwości kopiowania ich na inny, większy dysk, aby dodatkowo zmieścić proxy, poszukiwałem rozwiązania.  Zresztą,  kopiować tylko po to by tworzyć proxy było dla mnie bez sensu. 
Ale wracając do Blackmagic Proxy Generator, drugim trochę uciążliwym, przynajmniej dla mnie, jest wybór kodeków, w których można tworzyć docelowe proxy. Niestety jest tam raptem wybór kilku, być może logicznych, ale jednak nie każdemu może pasować ten wybór. To tak w skrócie geneza pomysłu na własne rozwiązanie.
Dodatkowym argumentem był fakt, że generowanie samym programem  DaVinci Resolve plików proxy wprawdzie umożliwia wskazanie miejsca docelowego, ale blokuje wtedy program. A już wisienką na torcie był fakt, że DaVinci mi się po prostu kilka razy zawiesił podczas generowania.

Tworzenie symlinków dla Blackmagic Proxy Generator

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:

      1 otwieramy i odczytujemy nazwę pierwsze katalogu zawierającego pliki video, na przykład "sowy"
      2 tworzymy w nim katalog "proxy"
      3 na dysku/katalogu docelowym tworzymy katalog z nazwą z punktu 1
      4 tworzymy symlink z aktualnego "proxy" na katalog z punktu 3
      5 przechodzimy rekursywnie w głąb drzewa katalogowego

    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.

    SymlinkCreator dla DaVinci Resolve i Blackmagic Proxy Generator

    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 :-)

    ProxyFileGenerator - czyli, jak ułatwić sobie życie

    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.

    ProxyFileGenerator - skrypt do generowania plików proxy

    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 :-)

    Poprawka na własną głupotę czyli jak łatwo naprawić swoje błędy :-)

    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" :-)

    Kolejna poprawka - tym razem nie moja wina :--)

    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ż :-)

    No i kolejny update

    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ć.

    Bądżmy w kontakcie Projektika Bądżmy w kontakcie

    PROJEKTIKA s.r.o.

    CZ +420 604 871 419

    PL +48 574 244 912

    office@projektika.group

     

    IČ: 23010134

    DIČ: CZ23010134

    Nejsme plátcem DPH

    projektika maps