#!/bin/bash # import_masterclass.sh # Creates a Masterclass.library in Eagle format from /volume1/steve/Courses/masterclass/ # Run this ON the Synology NAS (ssh in first) # # Eagle library structure: # Masterclass.library/ # metadata.json <- library-level metadata (folders tree) # images/ # .info/ # # _thumbnail.png (generated by ffmpeg for video; copied for images) # metadata.json set -e SOURCE="/volume1/steve/Courses/masterclass" LIBRARY="/volume1/steve/Eagle_Libraries/Masterclass.library" IMAGES="$LIBRARY/images" # Timestamp-based unique ID generator (Eagle uses alphanumeric IDs) # We'll use a counter-based approach with a base36-like prefix ID_COUNTER=0 BASE_ID="MC$(date +%s | tail -c 8)" gen_id() { ID_COUNTER=$((ID_COUNTER + 1)) printf '%s%06d' "$BASE_ID" "$ID_COUNTER" } # Get video duration in seconds via ffprobe get_duration() { local file="$1" ffprobe -v quiet -print_format json -show_streams "$file" 2>/dev/null \ | grep '"duration"' | head -1 | grep -oE '[0-9]+(\.[0-9]+)?' | head -1 || echo "0" } # Get image/video dimensions get_dimensions() { local file="$1" ffprobe -v quiet -print_format json -show_streams "$file" 2>/dev/null \ | grep -E '"width"|"height"' | head -2 \ | grep -oE '[0-9]+' | head -2 | tr '\n' 'x' | sed 's/x$//' || echo "0x0" } # Mime/ext helpers ext_of() { echo "${1##*.}" | tr '[:upper:]' '[:lower:]'; } is_video() { case "$(ext_of "$1")" in mp4|mov|m4v|avi|mkv|webm) return 0;; esac; return 1 } is_image() { case "$(ext_of "$1")" in jpg|jpeg|png|gif|webp|svg) return 0;; esac; return 1 } is_audio() { case "$(ext_of "$1")" in mp3|m4a|aac|wav|flac) return 0;; esac; return 1 } echo "=== Creating Masterclass Eagle Library ===" echo "Source: $SOURCE" echo "Library: $LIBRARY" # --- Build folder tree from directory structure --- # Walk category/course dirs and assign IDs declare -A FOLDER_IDS # path -> id FOLDERS_JSON="" build_folder_json() { local dirpath="$1" local name="$2" local children_json="$3" local fid="${FOLDER_IDS[$dirpath]}" local now=$(date +%s%3N) echo "{\"id\":\"$fid\",\"name\":\"$name\",\"description\":\"\",\"children\":[$children_json],\"modificationTime\":$now,\"tags\":[],\"password\":\"\",\"passwordTips\":\"\"}" } # Pre-assign IDs to all category and course folders for category_dir in "$SOURCE"/*/; do [[ "$(basename "$category_dir")" == "@eaDir" ]] && continue category="$(basename "$category_dir")" FOLDER_IDS["$category_dir"]="$(gen_id)" for course_dir in "$category_dir"*/; do [[ "$(basename "$course_dir")" == "@eaDir" ]] && continue [[ ! -d "$course_dir" ]] && continue # Some courses have sub-subdirs (like mp3/, guide/) — treat those as leaf FOLDER_IDS["$course_dir"]="$(gen_id)" done done # Build nested folder JSON TOP_FOLDERS="" for category_dir in "$SOURCE"/*/; do [[ "$(basename "$category_dir")" == "@eaDir" ]] && continue category="$(basename "$category_dir")" cat_id="${FOLDER_IDS[$category_dir]}" COURSE_FOLDERS="" for course_dir in "$category_dir"*/; do [[ "$(basename "$course_dir")" == "@eaDir" ]] && continue [[ ! -d "$course_dir" ]] && continue course="$(basename "$course_dir")" course_id="${FOLDER_IDS[$course_dir]}" now=$(date +%s%3N) course_json="{\"id\":\"$course_id\",\"name\":\"$course\",\"description\":\"\",\"children\":[],\"modificationTime\":$now,\"tags\":[],\"password\":\"\",\"passwordTips\":\"\"}" COURSE_FOLDERS="${COURSE_FOLDERS:+$COURSE_FOLDERS,}$course_json" done now=$(date +%s%3N) cat_json="{\"id\":\"$cat_id\",\"name\":\"$category\",\"description\":\"\",\"children\":[$COURSE_FOLDERS],\"modificationTime\":$now,\"tags\":[],\"password\":\"\",\"passwordTips\":\"\"}" TOP_FOLDERS="${TOP_FOLDERS:+$TOP_FOLDERS,}$cat_json" done # --- Create library structure --- mkdir -p "$IMAGES" now=$(date +%s%3N) cat > "$LIBRARY/metadata.json" <//... or $SOURCE////... rel="${filepath#$SOURCE/}" category="$(echo "$rel" | cut -d'/' -f1)" course="$(echo "$rel" | cut -d'/' -f2)" course_dir="$SOURCE/$category/$course/" folder_id="${FOLDER_IDS[$course_dir]}" [[ -z "$folder_id" ]] && folder_id="${FOLDER_IDS[$SOURCE/$category/]}" # Generate item ID item_id="$(gen_id)" item_dir="$IMAGES/${item_id}.info" mkdir -p "$item_dir" # Hardlink the file into the .info dir (saves space) ln "$filepath" "$item_dir/$filename" 2>/dev/null || cp "$filepath" "$item_dir/$filename" # Get metadata size=$(stat -c%s "$filepath" 2>/dev/null || echo 0) btime=$(stat -c%W "$filepath" 2>/dev/null || date +%s) mtime=$(stat -c%Y "$filepath" 2>/dev/null || date +%s) btime_ms=$((btime * 1000)) mtime_ms=$((mtime * 1000)) now_ms=$(date +%s%3N) width=0; height=0; duration_val="null" if is_video "$filepath"; then dims=$(get_dimensions "$filepath") width=$(echo "$dims" | cut -dx -f1) height=$(echo "$dims" | cut -dx -f2) dur=$(get_duration "$filepath") [[ -n "$dur" && "$dur" != "0" ]] && duration_val="$dur" # Generate thumbnail at 10% into video thumb_file="$item_dir/${name}_thumbnail.png" ffmpeg -v quiet -ss "$(echo "$dur * 0.1" | awk '{printf "%.0f", $1}')" \ -i "$filepath" -vframes 1 -vf "scale=320:-1" "$thumb_file" 2>/dev/null || true elif is_image "$filepath"; then dims=$(get_dimensions "$filepath") width=$(echo "$dims" | cut -dx -f1) height=$(echo "$dims" | cut -dx -f2) # Use the image itself as thumbnail (resized) thumb_file="$item_dir/${name}_thumbnail.png" ffmpeg -v quiet -i "$filepath" -vf "scale=320:-1" "$thumb_file" 2>/dev/null || \ cp "$filepath" "$thumb_file" 2>/dev/null || true fi # Build metadata.json folder_arr="[]" [[ -n "$folder_id" ]] && folder_arr="[\"$folder_id\"]" cat > "$item_dir/metadata.json" <