Ignore current directory

Support for unRAID and docker container users
Post Reply
DJArbz
Posts: 2
Joined: 27 Nov 2024, 14:41

Ignore current directory

Post by DJArbz »

I am working on a custom bash script to handle the renaming of my media.
I have discovered part of my issue being that filebot will see Argument[0] as the current directory, which causes an Invalid Usage error when the current directory is '/'.

Code: Select all

root@filebot-movie-watcher:/# /opt/bin/filebot-watcher
[2024-11-27 08:22:27] [INFO] ********************************************************************************
[2024-11-27 08:22:27] [INFO] Starting FileBot automation script
[2024-11-27 08:22:30] [INFO] ********************************************************************************
[2024-11-27 08:22:30] [INFO] Watching [/media/watch] for new files...
[2024-11-27 08:22:30] [INFO] Processing video file: /media/watch/8 Mile (2002) (tmdb-65)/8 Mile (2002) [ID=65.1080p.HEVC.x265.SDR .10bit.Opus.6ch.R.3.2 Mbps].mkv
[2024-11-27 08:22:35] [INFO] Executing FileBot command for: /media/watch/8 Mile (2002) (tmdb-65)/
[2024-11-27 08:22:35] [ERROR] FileBot processing failed for: /media/watch/8 Mile (2002) (tmdb-65)/
[2024-11-27 08:22:35] [ERROR] Filebot Command: filebot @/tmp/filebot.args
[2024-11-27 08:22:35] [ERROR] /opt/bin/filebot-watcher: line 216: filebot @/tmp/filebot.args: No such file or directory
[2024-11-27 08:22:35] [ERROR] Filebot Args file [/tmp/filebot.args]:
[2024-11-27 08:22:35] [ERROR]
-script
fn:amc
/media/watch/8 Mile (2002) (tmdb-65)/
--action
test
--output
/media/Movies/
--conflict
auto
-non-strict
--log-file
/data/filebot/logs/amc.2024-11-27.log
--log
all
-no-xattr
--def
unsorted=y
music=n
artwork=y
movieFormat={hd}/{~plex.id % {" {imdb-"+imdbid+"}"} % {" {edition-"+edition+"}" } }{ fn.match(/\\[[A-Z]*\\]/) }{ fn.match(/\\[[A-Za-z]*[ -][a-zA-Z0-9]*\\]/) }{" [" + s3d + "]"}{" [" + hdr + "]"}[{aco}{channels}][{vc}]{"-" + group}
subtitles=en
plex=plex:<REDACTED>
reportError=y
clean=y
movieDB=TheMovieDB
storeReport=/data/filebot/reports
discord=https://discord.com/api/webhooks/<REDACTED>
root@filebot-movie-watcher:/# filebot @/tmp/filebot.args
Run script [fn:amc] at [Wed Nov 27 08:22:40 CST 2024]
Parameter: unsorted = y
Parameter: music = n
Parameter: artwork = y
Parameter: movieFormat = {hd}/{~plex.id % {" {imdb-"+imdbid+"}"} % {" {edition-"+edition+"}" } }{ fn.match(/\\[[A-Z]*\\]/) }{ fn.match(/\\[[A-Za-z]*[ -][a-zA-Z0-9]*\\]/) }{" [" + s3d + "]"}{" [" + hdr + "]"}[{aco}{channels}][{vc}]{"-" + group}
Parameter: subtitles = en
Parameter: plex = *****
Parameter: reportError = y
Parameter: clean = y
Parameter: movieDB = TheMovieDB
Parameter: storeReport = /data/filebot/reports
Parameter: discord = *****
Argument[0]: /
Argument[1]: /media/watch/8 Mile (2002) (tmdb-65)
[TEST] --def artwork is incompatible with --action TEST and has been disabled
[TEST] --def clean is incompatible with --action TEST and has been disabled
[TEST] --def unsorted is incompatible with --action TEST and has been disabled
Invalid usage: output folder [/media/Movies] must not be the same as or be inside of input folder [/, /media/watch/8 Mile (2002) (tmdb-65)]
Abort (×_×)
You'll notice arguments:

Code: Select all

Argument[0]: /
Argument[1]: /media/watch/8 Mile (2002) (tmdb-65)
And the error:

Code: Select all

Invalid usage: output folder [/media/Movies] must not be the same as or be inside of input folder [/, /media/watch/8 Mile (2002) (tmdb-65)]
Also Sysenv:

Code: Select all

root@filebot-movie-watcher:/# filebot -script fn:sysenv

# Local Time #
Wed Nov 27 08:27:36 CST 2024

# Process Tree #
/usr/bin/bash
└─ /usr/bin/dash
   └─ /usr/lib/jvm/java-21-openjdk-amd64/bin/java

# Environment Variables #
AMC_ACTION: test
AMC_ARGS: -no-xattr
AMC_ARTWORK: y
AMC_CONFLICT: auto
AMC_DEFS: subtitles=en plex=plex:<REDACTED> reportError=y clean=y movieDB=TheMovieDB storeReport=/data/filebot/reports discord=https://discord.com/api/webhooks/<REDACTED>
AMC_FORMAT_MOVIE: {hd}/{~plex.id % {" {imdb-"+imdbid+"}"} % {" {edition-"+edition+"}" } }{ fn.match(/\\[[A-Z]*\\]/) }{ fn.match(/\\[[A-Za-z]*[ -][a-zA-Z0-9]*\\]/) }{" [" + s3d + "]"}{" [" + hdr + "]"}[{aco}{channels}][{vc}]{"-" + group}
AMC_MUSIC: n
AMC_OUT_DIR: /media/Movies/
AMC_UNSORTED: y
FILEBOT_VERSION: 5.1.6
HOME: /data
HOSTNAME: filebot-movie-watcher
LANG: C.UTF-8
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PGID: 568
PGROUP: filebot
PUID: 568
PUSER: filebot
PWD: /
SETTLE_DOWN_CHECK: 5 seconds ago
SETTLE_DOWN_TIME: 300
SHLVL: 1
TERM: xterm
TZ: America/Chicago
UMASK: 002
_: /usr/bin/filebot

# Java System Properties #
application.cache: /data/cache
application.deployment: docker
application.dir: /data
awt.useSystemAAFontSettings: on
file.encoding: UTF-8
file.separator: /
grape.root: /data/grape
http.agent: FileBot/5.1.6
java.class.path: /usr/share/filebot/jar/filebot.jar
java.class.version: 65.0
java.home: /usr/lib/jvm/java-21-openjdk-amd64
java.io.tmpdir: /tmp
java.library.path: /usr/lib/x86_64-linux-gnu/jni
java.net.useSystemProxies: false
java.runtime.name: OpenJDK Runtime Environment
java.runtime.version: 21.0.5+11-Ubuntu-1ubuntu124.10
java.specification.name: Java Platform API Specification
java.specification.vendor: Oracle Corporation
java.specification.version: 21
java.vendor: Ubuntu
java.vendor.url: https://ubuntu.com/
java.vendor.url.bug: https://bugs.launchpad.net/ubuntu/+source/openjdk-21
java.version: 21.0.5
java.version.date: 2024-10-15
java.vm.compressedOopsMode: Zero based
java.vm.info: mixed mode, sharing
java.vm.name: OpenJDK 64-Bit Server VM
java.vm.specification.name: Java Virtual Machine Specification
java.vm.specification.vendor: Oracle Corporation
java.vm.specification.version: 21
java.vm.vendor: Ubuntu
java.vm.version: 21.0.5+11-Ubuntu-1ubuntu124.10
jdk.debug: release
jdk.logger.packages: net.filebot.Logging
jdk.module.path: /usr/share/openjfx/lib
jna.boot.library.name: jnidispatch.system
jna.boot.library.path: /usr/lib/x86_64-linux-gnu/jni
jna.library.path: /usr/lib/x86_64-linux-gnu/jni
jna.noclasspath: true
jna.nosys: false
jna.nounpack: true
line.separator:

native.encoding: UTF-8
net.filebot.WebServices.OpenSubtitles.v1: true
net.filebot.archive.extractor: ShellExecutables
net.filebot.gio.GVFS: /gvfs
org.apache.commons.logging.Log: org.apache.commons.logging.impl.NoOpLog
os.arch: amd64
os.name: Linux
os.version: 6.6.44-production+truenas
path.separator: :
prism.order: sw
stderr.encoding: UTF-8
stdout.encoding: UTF-8
sun.arch.data.model: 64
sun.boot.library.path: /usr/lib/jvm/java-21-openjdk-amd64/lib
sun.cpu.endian: little
sun.io.unicode.encoding: UnicodeLittle
sun.java.command: /usr/share/filebot/jar/filebot.jar -script fn:sysenv
sun.java.launcher: SUN_STANDARD
sun.jnu.encoding: UTF-8
sun.management.compiler: HotSpot 64-Bit Tiered Compilers
sun.net.client.defaultConnectTimeout: 10000
sun.net.client.defaultReadTimeout: 60000
unixfs: false
useCreationDate: false
useExtendedFileAttributes: true
useGVFS: true
user.dir: /
user.home: /data
user.language: en
user.name: root
user.timezone: America/Chicago

# Arguments #
args[0] = -script
args[1] = fn:sysenv
Done ヾ(@⌒ー⌒@)ノ
Finally, the current version of my WIP script:

Code: Select all

#!/bin/bash
# Improved FileBot Automation Script

# Strict mode for better error handling
set -euo pipefail

# Default configuration with sensible defaults
declare -A CONFIG=(
  [AMC_MAX_WAIT_TIME]=300
  [AMC_BACKOFF_MULTIPLIER]=1
  [AMC_ACTION]=duplicate
  [AMC_CONFLICT]=auto
  [AMC_WATCH_DIR]=/media/watch
  [AMC_OUT_DIR]=/media/filebot
  [AMC_UNSORTED]=y
  [AMC_MUSIC]=y
  [AMC_ARTWORK]=y
  [AMC_LOG_DIR]=/data/filebot/logs
  [AMC_LOG_FILE_PREFIX]=amc
  [AMC_LOG_LEVEL]=all
  [AMC_LOG_MAX_AGE]=7
  [AMC_EXCLUDE_LIST]=.excludes
  [AMC_FILEBOT_ARGS_FILE]=/tmp/filebot.args
)

get_log_file() {
  today=$(date +%Y-%m-%d)
  echo "${CONFIG[AMC_LOG_DIR]}/${CONFIG[AMC_LOG_FILE_PREFIX]}.${today}.log"
}

# Logging function with timestamp and severity levels
log() {
  local severity="${1}"
  local message="${2}"
  local log_file
  log_file="$(get_log_file)"

  if [ "${severity}" == "TRACE" ]; then
    # Log TRACE messages to the log file only.
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${severity}] ${message}" >> "${log_file}"
  else
    # Log to file and STDERR so that function log output does not get captured in commands.
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${severity}] ${message}" | tee -a "${log_file}" >&2
  fi
}

# Error handling function
error_exit() {
  log "ERROR" "${1}"
  exit 1
}

# Validate required directories
validate_directories() {
  [[ -d "${CONFIG[AMC_WATCH_DIR]}" ]] || error_exit "Watch directory does not exist: ${CONFIG[AMC_WATCH_DIR]}"
  [[ -d "${CONFIG[AMC_OUT_DIR]}" ]] || error_exit "Output directory does not exist: ${CONFIG[AMC_OUT_DIR]}"
  [[ -d "${CONFIG[AMC_OUT_DIR]}/Unsorted" ]] || error_exit "Unsorted output directory does not exist: ${CONFIG[AMC_OUT_DIR]}/Unsorted"
}

# Build FileBot command dynamically
build_filebot_command() {
  local process_path="$1"
  shift

  local command=(
    "filebot"
    #"-script" "fn:sysenv"
    "-script" "fn:amc" "'${process_path}'"
    "--action" "${CONFIG[AMC_ACTION]}"
    "--output" "${CONFIG[AMC_OUT_DIR]}"
    "--conflict" "${CONFIG[AMC_CONFLICT]}"
    "-non-strict"
    "--log-file" "'$(get_log_file)'"
    "--log" "${CONFIG[AMC_LOG_LEVEL]}"
  )

  # Optional Arguments
  [[ -n "${AMC_ARGS:-}" ]] && command+=("${AMC_ARGS}")

  ## --def options below
  command+=("--def")
  [[ -n "${CONFIG[AMC_UNSORTED]:-}" ]]  && command+=("unsorted=${CONFIG[AMC_UNSORTED]}")
  [[ -n "${CONFIG[AMC_MUSIC]:-}" ]]     && command+=("music=${CONFIG[AMC_MUSIC]}")
  [[ -n "${CONFIG[AMC_ARTWORK]:-}" ]]   && command+=("artwork=${CONFIG[AMC_ARTWORK]}")

  # Optional format overrides
  [[ -n "${AMC_FORMAT_MOVIE:-}" ]]    && command+=("movieFormat='${AMC_FORMAT_MOVIE}'")
  [[ -n "${AMC_FORMAT_SERIES:-}" ]]   && command+=("seriesFormat='${AMC_FORMAT_SERIES}'")
  [[ -n "${AMC_FORMAT_ANIME:-}" ]]    && command+=("animeFormat='${AMC_FORMAT_ANIME}'")
  [[ -n "${AMC_FORMAT_MUSIC:-}" ]]    && command+=("musicFormat='${AMC_FORMAT_MUSIC}'")
  [[ -n "${AMC_FORMAT_UNSORTED:-}" ]] && command+=("unsortedFormat='${AMC_FORMAT_UNSORTED}'")

  # Additional optional parameters
  [[ -n "${AMC_DEFS:-}" ]] && command+=("${AMC_DEFS}")
  [[ -n "${CONFIG[AMC_EXCLUDE_LIST]:-}" && -f "${CONFIG[AMC_EXCLUDE_LIST]}" ]] && command+=("excludeList='${CONFIG[AMC_EXCLUDE_LIST]}'")

  ## Add remaining args and processing path
  # command+=("'${process_path}'")

  echo "${command[@]}"
}

# Build FileBot File dynamically
build_filebot_file() {
  local process_path="$1"
  shift

  echo > "${CONFIG[AMC_FILEBOT_ARGS_FILE]}"
  {
    echo "-script";
    echo "fn:amc";
    echo "${process_path}";
    echo "--action";
    echo "${CONFIG[AMC_ACTION]}";
    echo "--output";
    echo "${CONFIG[AMC_OUT_DIR]}";
    echo "--conflict";
    echo "${CONFIG[AMC_CONFLICT]}";
    echo "-non-strict";
    echo "--log-file";
    get_log_file;
    echo "--log";
    echo "${CONFIG[AMC_LOG_LEVEL]}";
    echo "-no-xattr";
    echo "--def";
    [[ -n "${CONFIG[AMC_UNSORTED]:-}" ]]  && echo "unsorted=${CONFIG[AMC_UNSORTED]}";
    [[ -n "${CONFIG[AMC_MUSIC]:-}" ]]     && echo "music=${CONFIG[AMC_MUSIC]}";
    [[ -n "${CONFIG[AMC_ARTWORK]:-}" ]]   && echo "artwork=${CONFIG[AMC_ARTWORK]}";
    [[ -n "${CONFIG[AMC_EXCLUDE_LIST]:-}" && -f "${CONFIG[AMC_EXCLUDE_LIST]}" ]] && echo "excludeList='${CONFIG[AMC_EXCLUDE_LIST]}'";

    # Optional format overrides
    [[ -n "${AMC_FORMAT_MOVIE:-}" ]]    && echo "movieFormat=${AMC_FORMAT_MOVIE}";
    [[ -n "${AMC_FORMAT_SERIES:-}" ]]   && echo "seriesFormat=${AMC_FORMAT_SERIES}";
    [[ -n "${AMC_FORMAT_ANIME:-}" ]]    && echo "animeFormat=${AMC_FORMAT_ANIME}";
    [[ -n "${AMC_FORMAT_MUSIC:-}" ]]    && echo "musicFormat=${AMC_FORMAT_MUSIC}";
    [[ -n "${AMC_FORMAT_UNSORTED:-}" ]] && echo "unsortedFormat=${AMC_FORMAT_UNSORTED}";

    # Additional optional parameters
    # [[ -n "${AMC_DEFS:-}" ]] && echo "${AMC_DEFS}";
  } >> "${CONFIG[AMC_FILEBOT_ARGS_FILE]}"
  
  # Use eval and an array to handle quoted strings correctly
  local items
  eval "items=(${AMC_DEFS})"
  for item in "${items[@]}"; do
      echo "${item}" >> "${CONFIG[AMC_FILEBOT_ARGS_FILE]}"
  done;

  command=("filebot" "@${CONFIG[AMC_FILEBOT_ARGS_FILE]}")
  echo "${command[@]}"
}

# Exponential backoff with jitter
exponential_backoff() {
  local attempt="${1}"
  local multiplier="${2}"
  local max_sleep="${3}"
  local jitter=$((RANDOM % 10))
  local sleep_time=$((attempt * attempt * multiplier + jitter))

  [[ "${sleep_time}" -gt "${max_sleep}" ]] && sleep_time="${max_sleep}"

  log "INFO" "Backing off for ${sleep_time} seconds"
  sleep "${sleep_time}"
}

# Check if a file is stable and ready for processing
is_file_stable() {
  local file="${1}"
  local max_wait=60 # Maximum wait time for file stability
  local check_interval=5
  local last_size
  last_size=$(stat -c %s "${file}")

  for ((i = 0; i < max_wait; i += check_interval)); do
    sleep "${check_interval}"
    local current_size
    current_size=$(stat -c %s "${file}")

    [[ "${current_size}" -eq "${last_size}" ]] && return 0 # File is stable

    last_size="${current_size}"
  done

  return 1 # File is still changing after max wait time
}

# Process video files
process_video_file() {
  local file="${1}"
  local mime="${2}"

  if ! echo "${mime}" | grep -qE ': video/'; then
    log "DEBUG" "Skipping non-video file: ${file}"
    return 0
  fi

  log "INFO" "Processing video file: ${file}"

  is_file_stable "${file}" || {
    log "WARN" "File is unstable, skipping: ${file}"
    return 0
  }

  # Determine file path for processing
  local process_path
  process_path="$(dirname "${file}")/"
  [[ "${process_path%/}" == "${CONFIG[AMC_WATCH_DIR]%/}" ]] && process_path="${file}"

  log "INFO" "Executing FileBot command for: ${process_path}"

  local filebot_cmd
  mapfile -t filebot_cmd < <(build_filebot_file "${process_path}")

  # Capture the output of filebot_cmd
  if ! output=$("${filebot_cmd[@]}" 2>&1); then
      # Log the error output if the command fails
      log "ERROR" "FileBot processing failed for: ${process_path}"
      log "ERROR" "Filebot Command: ${filebot_cmd[*]}"
      log "ERROR" "${output}"
      if [[ -f "${CONFIG[AMC_FILEBOT_ARGS_FILE]}" ]]; then
        log "ERROR" "Filebot Args file [${CONFIG[AMC_FILEBOT_ARGS_FILE]}]:"
        log "ERROR" "$(cat "${CONFIG[AMC_FILEBOT_ARGS_FILE]}")"
      fi
      return 1
  else
      # Log the success output if the command succeeds
      log "INFO" "FileBot processing succeeded."
      log "INFO" "${output}"
  fi

  # Clean up empty directories
  if [[ -d "${process_path}" ]]; then
    log "INFO" "Cleaning up directory: ${process_path}"
    find "${process_path}" -type d -empty -delete
  fi
}

# Recursive directory cleanup
cleanup_directories() {
  local directory="${1}"
  local max_depth="${2:-5}" # Prevent excessive recursion

  [[ "${max_depth}" -le 0 ]] && return 1

  log "DEBUG" "Cleaning directory: ${directory}"

  # Remove empty directories
  find "${directory}" -mindepth 1 -type d -empty -not -path '.recycle' -delete

  # Recursively clean subdirectories
  find "${directory}"  -mindepth 1 -maxdepth 1 -type d | while read -r subdir; do
    [[ "${subdir}" == "${directory}" ]] && continue
    cleanup_directories "${subdir}" $((max_depth - 1))
  done
}

# Main processing loop
main() {
  local log_file
  log_file="$(get_log_file)"

  log "INFO" "********************************************************************************"
  log "INFO" "Starting FileBot automation script"
  validate_directories

  # Capture the output of filebot_cmd
  if ! output=$(filebot -script fn:sysenv --log-file "${log_file}" 2>&1); then
      # Log the error output if the command fails
      log "ERROR" "Failed to check filebot Environment."
      log "ERROR" "${output}"
      return 1
  else
      # Log the success output if the command succeeds
      log "TRACE" "FileBot Environment."
      log "TRACE" "${output}"
  fi

  local runs=0
  while true; do
    log "INFO" "********************************************************************************"
    # Delete old log files
    find "$(dirname "${log_file}")" \
      -maxdepth 1 \
      -name "${CONFIG[AMC_LOG_FILE_PREFIX]}*.log" \
      -mtime "+${CONFIG[AMC_LOG_MAX_AGE]}" \
      -type f \
      -delete \
      -exec echo "Deleting old log: [{}]" \;
    runs=$((runs+1))
    log "INFO" "Watching [${CONFIG[AMC_WATCH_DIR]}] for new files..."

    # Process video files
    find "${CONFIG[AMC_WATCH_DIR]}" -type f \
      -not -path '.recycle' \
      -not -iname '.plexmatch' \
      | while read -r file; do
        mime=$(file --mime-type "${file}")
        process_video_file "${file}" "${mime}"
      done

    # Remove Plex match files
    find "${CONFIG[AMC_WATCH_DIR]}" -type f -iname '.plexmatch' -delete

    # Cleanup directories
    cleanup_directories "${CONFIG[AMC_WATCH_DIR]}"

    # Intelligent backoff
    exponential_backoff "${runs}" "${CONFIG[AMC_BACKOFF_MULTIPLIER]}" "${CONFIG[AMC_MAX_WAIT_TIME]}"
    log "INFO" "********************************************************************************"
  done
}

# Allow configuration via environment variables
for key in "${!CONFIG[@]}"; do
  CONFIG[${key}]="${!key:-${CONFIG[${key}]}}"
done

if [ "${AMC_ACTION}" = "rsyncmove" ]; then
  # shellcheck disable=SC2016
  CONFIG[AMC_ACTION]="'{ from, to -> rsync -a --no-owner --no-group --no-perms --remove-source-files --progress \"\$from\" \"\$to\" }'"
fi

# Execute main function
main
Side question, is there a way to move without preserving permissions like this custom action does?

Code: Select all

if [ "${AMC_ACTION}" = "rsyncmove" ]; then
  # shellcheck disable=SC2016
  CONFIG[AMC_ACTION]="'{ from, to -> rsync -a --no-owner --no-group --no-perms --remove-source-files --progress \"\$from\" \"\$to\" }'"
fi
User avatar
rednoah
The Source
Posts: 23694
Joined: 16 Nov 2011, 08:59
Location: Taipei
Contact:

Re: Ignore current directory

Post by rednoah »

DJArbz wrote: 27 Nov 2024, 14:53

Code: Select all

Argument[0]: /
Argument[1]: /media/watch/8 Mile (2002) (tmdb-65)
You are explicitly passing these input argument values on the command-line, likely by accident:

Shell: Select all

filebot ... "/" ... "/media/watch/8 Mile (2002) (tmdb-65)" ...


:idea: filebot notably does not use $PWD as default input argument:

Console Output: Select all

$ filebot -rename
No input arguments
No input files



EDIT:

:idea: Do you by any chance pass an empty argument?

Console Output: Select all

$ filebot -script fn:amc "" --output .
...
Argument[0]: /path/to/test
...
:!: I think you have an empty line in your @args file which is interpreted as valid empty string argument value which when interpreted as file path is apparently interpreted as the current working directory.
:idea: Please read the FAQ and How to Request Help.
DJArbz
Posts: 2
Joined: 27 Nov 2024, 14:41

Re: Ignore current directory

Post by DJArbz »

Thank you for the reply, you were correct.
My script was adding an empty line at the top of the args file.
Post Reply