studio graficzne, wstega Mobiusa, Projektika Group

Generování proxy pro DaVinci Resolve

obcházení omezení Blackmagic Proxy Generator

Blackmagic Proxy Generator

Skvělý program, doplněk k Resolvu, umožňující vytvářet proxy soubory na pozadí, bez zapojení hlavního programu. Jeho poměrně velkým omezením je však striktní pravidlo pro vytváření proxy adresářů. Ty jsou vždy umístěny v adresáři obsahujícím video soubory. Vždy tedy máme takovou strukturu:

/rootDir
  -> /videoDir
     -> /Proxy

Což je vlastně docela logické, ale život má své temné stránky. Za prvé, možná nechceme mít proxy na tomto disku, za druhé, prostě na něm není místo. Tak tomu bylo i v mém případě. Surové materiály, tedy zdrojové materiály, byly na externím SSD disku, poměrně velkém, protože měl 2 TB, ale byl plný. Zbývalo asi 50 MB. Přibližně 1400 souborů pro přírodopisný film. V několika různých formátech a rozlišeních. Nic mimořádného, pocházely z různých kamer, fotoaparátů, některé z telefonů. A protože jsem neměl možnost je zkopírovat na jiný, větší disk, aby se tam vešly i proxy, hledal jsem řešení. Kopírovat jen proto, abych vytvořil proxy, mi navíc nedávalo smysl.

Ale zpět k Blackmagic Proxy Generator, druhým trochu nepříjemným, alespoň pro mě, je výběr kodeků, ve kterých lze vytvářet cílové proxy. Bohužel je tam na výběr jen několik, možná logických, ale ne každému může tento výběr vyhovovat. To je ve zkratce geneze nápadu na vlastní řešení.

Dalším argumentem byla skutečnost, že generování proxy souborů samotným programem DaVinci Resolve sice umožňuje určit cílové umístění, ale zároveň blokuje program. A třešničkou na dortu byla skutečnost, že se mi DaVinci během generování několikrát jednoduše zhroutil.

Vytváření symlinků pro Blackmagic Proxy Generator

V první fázi jsem prostě přišel na běžné logické řešení: vytvoření symlinků. Jednoduše řečeno, symbolické odkazy z jednoho adresáře do jiného adresáře (pro ty, kteří tento pojem neznají, něco jako „zkratky“ k souborům). Nejprve jsem napsal skript, který zachytával požadavky generátoru na vytvoření proxy adresáře, zachytával tento požadavek a vytvářel „za běhu“ symbolický odkaz. Zdánlivě to fungovalo, zdánlivě ne. Někdy byl generátor rychlejší než skript a první proxy soubor uložil podle svého algoritmu, jindy se podařilo požadavek zachytit a správně propojit. 
Takže to nebyla správná cesta. A vůbec, jaký má smysl zachytávat požadavky na vytvoření adresáře, když vím, jaké budou potřeba?

Dalším krokem tedy bylo vytvoření předem stanovených symlinků a cílových adresářů v souladu se strukturou, kterou očekává DaVinci Resolve.
Abych to zkrátil, nakonec vznikl skript, který rekurzivně prochází katalog/disk zdrojových souborů, vytváří v cílovém umístění katalogy s názvy aktuálního katalogu a ve zdrojovém katalogu vytváří symlink s názvem „proxy“, který odkazuje právě na tento katalog. Je to trochu zamotané, ale nedokázal jsem to vysvětlit jednodušeji. Možná postupně, jak to funguje:

      1 otevřeme a přečteme název prvního adresáře obsahujícího video soubory, například „sowy“
      2 vytvoříme v něm adresář „proxy“
      3 na cílovém disku/adresáři vytvoříme adresář s názvem z bodu 1
      4 vytvoříme symlink z aktuálního „proxy“ na adresář z bodu 3
      5 rekurzivně procházíme adresářovou strukturou

    a tak dále, dokud nejsou vyčerpány všechny adresáře. V cílovém místě tedy máme ve skutečnosti zrcadlové kopie adresářů ze zdrojového disku, ale DaVinci Resolve nebo Blackmagic Proxy Generator je vidí jako správnou strukturu adresářů proxy. Samozřejmě by bylo možné vytvořit na cílovém disku/adresáři ještě správnější adresáře s původními názvy a teprve v nich adresáře proxy. Ale k čemu? To by bylo umění jen pro umění.

    Je třeba upozornit na jednu nevýhodu: skript hledá všude, takže v případě struktury XDCAM, kde jsou soubory v podadresáři /XDROOT/Clip, vytvoří odkaz na adresář Clip. Totéž platí pro ještě idiotičtější strukturu BPAV, kde každý video soubor má svůj vlastní adresář. Nejlepší je jednoduše přesunout video soubory do správného/nadřazeného/vlastního adresáře. Není třeba zde vysvětlovat, kdo se setkal s těmito formáty záznamu, ten ví, o co jde.

    SymlinkCreator pro DaVinci Resolve a Blackmagic Proxy Generator

    Toto je obsah skriptu k vytvoření proxy struktury pro Resolve, který jsem nakonec po několika pokusech vymyslel (málem jsem smazal zdrojové soubory, ale mám zálohu. Což doporučuji každému).
 Dokonce jsem přidal něco jako nápovědu, což normálně nedělám, a po několika měsících přemýšlím, jak se to sakra používalo :) Stačí zkopírovat textový obsah do souboru, například proxyDirCreator.sh, uložit ho někde na disk, přidělit mu práva k provedení a jednoduše používat. Samozřejmě to není program s úchvatně krásným grafickým rozhraním, ale skript pro spuštění v konzoli nebo terminálu. Možná není dokonalý, jako nic na této planetě, ale u mě funguje velmi dobře. A pokud vám něco nebude fungovat, vždy to můžete opravit. Malá poznámka: pokud chcete používat Bash, musíte mít alespoň verzi 4.1, jinak se bude při zápisu logů zhroutit. Bohužel Apple kvůli typu licence Bash používá i v nejnovějších strojích verzi 3.2.57. Prý proto, že bash od verze 4.0 je pod licencí GPL v3, kterou Apple nějak nemá rád. Proč? Kdo ví. Když se neví, o co jde, jde asi o peníze :-) Je to trochu škoda, ale na druhou stranu: Zsh je velmi kompatibilní s Bash, prakticky vše funguje bez problémů. A určitě tak jednoduché skripty, jako ten pro vytváření proxy adresářů.


    #!/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



    A tak máme hotové adresáře, ve kterých budou generovány proxy soubory, a to kdekoli. To v případě použití Blackmagic Proxy Generator. Jak již bylo řečeno, samotný Resolve DaVinci má možnost určit, kde mají být. Navíc, když potřebujeme přesunout proxy soubory, například na notebook, abychom během dovolené neztráceli čas na pláži :-), stačí zkopírovat katalog a máme kopii originálu. Není třeba provádět žádné extrahování proxy nebo podobné operace.


    Doporučuji používat možnost --log. Můžete zadat cestu s vlastním názvem souboru nebo pouze určit cílový adresář pro protokol. Pokud tedy použijeme syntaxi proxyCreator.sh --log DestinationDir SourceDir DestinationDir , dostaneme logy v cílovém adresáři, pojmenované „proxy_log_Aktuální-Datum-Hodina-Minuta.log”. Po několika týdnech nebo měsících se může ukázat, že tyto informace jsou docela užitečné. A při spuštění bez volby --exec získáme simulaci budoucích akcí, takže můžeme sledovat, co se bude dít. To se může hodit zejména při velkém počtu proxy adresářů, protože se může ukázat, že část adresářů má vygenerované své symlinky

    Ale protože jak se řeklo A, tak by se mělo říct i B, dalším krokem bylo vytvoření vlastního generátoru správných proxy video souborů s libovolným kodekem, velikostí nebo bitrate. Tedy technologický závod s firmou Blackmagic :-)

    ProxyFileGenerator – jak si ulehčit život

    Takže pokračujeme dál. Máme již krásnou strukturu proxy adresářů, je čas do nich nahrát nějaké soubory. Nejjednodušším řešením pro zpracování všech druhů video souborů z konzole je FFMPEG. Je to nenápadný program, ale umí v podstatě všechno. Možná kromě vaření a úklidu v domácnosti :-) Existuje mnoho jeho variant a modifikací, ale já osobně používám klasickou instalaci pomocí "brew install ffmpeg". Možná mají jiné verze někdy větší možnosti, ale já potřebuji jednoduchá řešení. Ano, například FFMBC je přímo stvořený pro profesionální zpracování videa, ale já vytvářím pouze proxy :-)

    Vraťme se k generování souborů. Tentokrát jsem se vzdal „Zsh“ ve prospěch obyčejného „Bash“. Prostě jsem měl jednoduchou verzi skriptu z minulých let a nechtěl jsem přepisovat formát, který byl sice kompatibilní, ale ne úplně. Detaily nejsou důležité, ale předávání argumentů jako tabulek nebo řetězců má svůj význam. Nebo tomu prostě nerozumím. Nechme to být, stejně nemám v úmyslu vést „flame war“ o tom, co je lepší. Někdy je prostě něco užitečné nebo hotové a jindy zase něco jiného. Na základě starého, několikřádkového skriptu tak vzniklo něco jako „kombajn” pro proxy soubory. Původně byl nápad spojit proxyDirCreator a proxyFileGenerator (tak jsem ty soubory hrdě pojmenoval :-), ale to by bylo naprosto nesmyslné. Ne každý potřebuje symlinkovat proxy adresáře na jiný disk. Skript tedy rekurzivně prochází zdrojový adresář, vyhledává soubory .mov, .mp4, .mxf (s takovými se nejčastěji setkávám v práci), pokud neexistuje, vytvoří v dané úrovni proxy adresář (pokud existuje, použije existující, ať už je to adresář nebo symlink), provede kompresi všech souborů z daného adresáře, přidá výsledek do souboru „.csv“ jako jakýsi log a pokračuje ve své cestě po adresářích. A tak pokračuje, dokud nezpracuje všechny soubory z dané lokality. Má několik možností, vše je uvedeno v kódu skriptu, takže nemá smysl to zde rozebírat. V každém případě, stejně jako v proxyDirCreator, ve výchozím nastavení nic nevytváří, pouze ukazuje, co by chtěl udělat. Vždy je lepší předem vidět, jaké změny má nějaký program v úmyslu provést, než pak plakat, že se něco pokazilo.

    ProxyFileGenerator – skript pro generování proxy souborů

    Pokud chcete generovat několik souborů najednou, je třeba nainstalovat GNU Parallel. Upřímně řečeno nevím, kolik jader FFMpeg při práci využívá, mám dojem, že pouze jedno. Znásobení počtu procesů tedy může výrazně zkrátit dobu generování a navíc můžete na ostatních jádrech klidně pracovat. Ale to je téma na samostatný článek. V každém případě mi to funguje a dokonce docela rychle. Používám však procesor Apple M4, který je sám o sobě poměrně rychlý. Neměl jsem možnost provést testy na jiných strojích.

    #!/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"

    Samozřejmě je možné skript rozšířit. Hodila by se například možnost jednoduše generovat proxy z A do B, bez vytváření odkazů nebo adresářů. Ale já jsem chtěl pouze simulovat fungování původního programu. Možností je nekonečné množství, každý uživatel má jistě jiné potřeby. No, nic není dokonalé :-)

    Druhá věc, kterou je třeba vysvětlit, je, že argumenty v jednom skriptu jsou na začátku, v druhém na konci příkazu. No, prostě to tak zůstalo z předchozí verze. Ani mě nenapadlo to opravit, aby to bylo stejné. Ale našli se lidé, kteří si toho všimli :-) Díky za připomínky, ale v zásadě to nijak nebrání používání. A navíc každá změna nám trochu pomáhá prolomit rutinu, takže náš mozek pracuje efektivněji :-)

    Oprava vlastní hlouposti, aneb jak snadno napravit své chyby :-)

    Po krátkém dobrodružství s ComfyUI (bohužel jsem ho z vlastní nepozornosti spustil v neizolovaném prostředí) mi smazal ffmpeg. No nic, věřím konzoli, spouštím „brew instal ...“, a tady nějaká závislost nemá ten správný SSL certifikát, nebo bůhví, co se pokazilo. Po krátkém přemýšlení jsem došel k závěru, že brew je sice pohodlné, ale v podstatě ho nepotřebuji. Potřebuji pouze FFMpeg a FFProbe. A protože na stránce evermeet.cx jsou k dispozici hotové binární soubory (v mém případě pro mac osx) pro oba tyto programy, nejjednodušší bude prostě použít hotový produkt, místo abych budoval celý strom závislostí. Možná je to trochu zamotané, jako všechny moje popisy, ale prostě nepotřebuji pokročilé kodeky pro běžné vytváření proxy. A ty, které jsou obsaženy v binárních verzích FFMpeg, jsou zcela dostačující.

    Nebudeme to protahovat, tady je hotový skript pro instalaci FFMpeg a FFProbe v nejnovější dostupné verzi, bez Homebrew, kompilace a dalších podivných věcí :-)

    #!/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"


    Stačí zkopírovat, vložit do souboru, udělit oprávnění k provedení a spustit v konzoli. Pravděpodobně se před instalací FFMpega zeptá na heslo, ale to se může lišit. A jak se to říká – „U mě všechno funguje“ :-)

    Oprava č. 2

    Bylo přidáno odstranění stezky z „attached pic“, což někdy způsobovalo podivné chování, zejména u souborů DJI. A u stejných souborů byl větší problém s časovými kódy. Osmo DJI má sice TC, ale označeno jako „codec:none“. Drony DJI nemají vůbec žádné TC, nanejvýš časové označení v názvu nebo záhlaví. Teoreticky by možnost „-map 0:d?“ měla ignorovat chybějící „path“, ale nefunguje to úplně.

    Právě v případě označení `codec:none`, což je podle mě naprostá chyba. Ale to je jen můj osobní názor, pravděpodobně to má nějaký skrytý smysl.

    V každém případě, pokud nutně potřebujete TC a obecně je časový kód prakticky vždy zaznamenán také jako metadata, přidejte „-map 0:d?“ do deklarace maps. A to je vše. Dalo by se to rozšířit, přidat výjimky, nějaké if a else. Ale univerzálnost má své meze. Stejně jako moje trpělivost :-)

    Buďme v kontaktu Projektika Buďme v kontaktu

    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