215 lines
7.5 KiB
Bash
215 lines
7.5 KiB
Bash
#!/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/
|
|
# <ID>.info/
|
|
# <original-file>
|
|
# <name>_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" <<EOF
|
|
{"folders":[$TOP_FOLDERS],"smartFolders":[],"quickAccess":[],"tagsGroups":[],"modificationTime":$now,"applicationVersion":"4.0.0"}
|
|
EOF
|
|
|
|
echo "Library metadata written."
|
|
|
|
# --- Import files ---
|
|
TOTAL=0
|
|
DONE=0
|
|
SKIPPED=0
|
|
|
|
# Count total files to import
|
|
TOTAL=$(find "$SOURCE" -type f -not -path '*/@eaDir/*' \
|
|
\( -name "*.mp4" -o -name "*.mov" -o -name "*.m4v" \
|
|
-o -name "*.mp3" -o -name "*.m4a" \
|
|
-o -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" -o -name "*.webp" \
|
|
-o -name "*.pdf" \) | wc -l)
|
|
echo "Total files to import: $TOTAL"
|
|
|
|
find "$SOURCE" -type f -not -path '*/@eaDir/*' \
|
|
\( -name "*.mp4" -o -name "*.mov" -o -name "*.m4v" \
|
|
-o -name "*.mp3" -o -name "*.m4a" \
|
|
-o -name "*.jpg" -o -name "*.jpeg" -o -name "*.png" -o -name "*.webp" \
|
|
-o -name "*.pdf" \) | sort | while read filepath; do
|
|
|
|
filename="$(basename "$filepath")"
|
|
ext="$(ext_of "$filepath")"
|
|
# Name without extension
|
|
name="${filename%.*}"
|
|
# Clean up name: replace underscores/hyphens with spaces, strip leading numbers
|
|
pretty_name="$(echo "$name" | sed 's/[-_]/ /g' | sed 's/^ *[0-9]*[a-z]* //')"
|
|
[[ -z "$pretty_name" ]] && pretty_name="$name"
|
|
|
|
# Determine folder ID from path
|
|
# Path: $SOURCE/<category>/<course>/... or $SOURCE/<category>/<course>/<subdir>/...
|
|
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" <<METAEOF
|
|
{"id":"$item_id","name":"$pretty_name","size":$size,"btime":$btime_ms,"mtime":$mtime_ms,"ext":"$ext","tags":[],"folders":$folder_arr,"isDeleted":false,"url":"","annotation":"","modificationTime":$now_ms,"width":$width,"height":$height,"resolutionWidth":$width,"resolutionHeight":$height${duration_val:+,"duration":$duration_val},"lastModified":$now_ms}
|
|
METAEOF
|
|
|
|
DONE=$((DONE + 1))
|
|
if (( DONE % 50 == 0 )); then
|
|
echo "Progress: $DONE / $TOTAL"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "=== Import complete ==="
|
|
echo "Library: $LIBRARY"
|
|
ls "$IMAGES" | wc -l
|
|
echo "items imported."
|