-
-
Save brokosz/87be2f15e58aeb826d1696dd76771bdb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Audio to M4B Audiobook Converter | |
| # Converts multiple audio files per book into single M4B files with metadata | |
| set -e | |
| # Default settings | |
| DEFAULT_BITRATE="copy" | |
| DEFAULT_CHAPTER_LENGTH=300 | |
| VERBOSE=false | |
| QUIET=false | |
| OUTPUT_TO_PARENT=true | |
| OUTPUT_DIR="" | |
| OVERRIDE_AUTHOR="" | |
| OVERRIDE_TITLE="" | |
| NO_LOOKUP=false | |
| FAST_MODE=false | |
| FORCE_CONCAT=false | |
| show_help() { | |
| cat << 'EOF' | |
| Audio to M4B Audiobook Converter | |
| USAGE: | |
| m4b [OPTIONS] <path> | |
| OPTIONS: | |
| -b, --bitrate RATE Audio bitrate (default: copy - preserves original) | |
| -c, --chapter-time SEC Chapter length in seconds (default: 300) | |
| -o, --output-dir DIR Output directory for M4B files | |
| -p, --parent Output M4B to parent directory (default) | |
| -s, --same-dir Output M4B to same directory as audio files | |
| -a, --author AUTHOR Override author metadata | |
| -t, --title TITLE Override title metadata | |
| --no-lookup Disable online metadata lookup | |
| --fast Fast container-only conversion (no transcoding) | |
| --force-concat Force multi-file processing even for single files | |
| -v, --verbose Show ffmpeg output and detailed progress | |
| -q, --quiet Minimal output (errors only) | |
| -h, --help Show this help | |
| EXAMPLES: | |
| m4b ~/Books/Book-Title/ | |
| m4b -s ~/Books/ | |
| m4b -a "Author Name" -t "Book Title" book-folder/ | |
| m4b -o ~/Converted ~/Books/ | |
| m4b --fast ~/Books/single-file-book/ | |
| REQUIREMENTS: | |
| - ffmpeg with AAC support | |
| - Each book should be in its own folder | |
| - Audio files will be processed in alphabetical order | |
| - curl (for online metadata lookup) | |
| SUPPORTED FORMATS: | |
| - MP3, AAC, M4A, MP4, FLAC, OGG, WAV | |
| - Automatically detects and converts from any supported format | |
| METADATA PRIORITY: | |
| 1. Manual overrides (-a, -t flags) | |
| 2. Audio metadata tags (if meaningful) | |
| 3. Multi-file filename pattern analysis | |
| 4. Folder name as fallback | |
| OUTPUT: | |
| Creates .m4b files with embedded chapters and metadata | |
| EOF | |
| } | |
| log_info() { | |
| if [[ "$QUIET" != "true" ]]; then | |
| echo "$@" | |
| fi | |
| } | |
| log_verbose() { | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| echo "$@" | |
| fi | |
| } | |
| get_output_path() { | |
| local book_dir="$1" | |
| local book_name="$2" | |
| if [[ -n "$OUTPUT_DIR" ]]; then | |
| echo "$OUTPUT_DIR/${book_name}.m4b" | |
| elif [[ "$OUTPUT_TO_PARENT" == "true" ]]; then | |
| echo "${book_dir}.m4b" | |
| else | |
| echo "${book_dir}/${book_name}.m4b" | |
| fi | |
| } | |
| check_dependencies() { | |
| if ! command -v ffmpeg &> /dev/null; then | |
| echo "Error: ffmpeg not found. Install with: brew install ffmpeg" | |
| exit 1 | |
| fi | |
| if ! command -v curl &> /dev/null && [[ "$NO_LOOKUP" != "true" ]]; then | |
| log_verbose "Warning: curl not found. Online lookup disabled." | |
| NO_LOOKUP=true | |
| fi | |
| } | |
| analyze_filename_patterns() { | |
| local audio_files=("$@") | |
| local title="" | |
| local author="" | |
| # Only analyze if we have multiple files | |
| if [[ ${#audio_files[@]} -lt 2 ]]; then | |
| echo "|" | |
| return | |
| fi | |
| # Extract basenames without extensions | |
| local basenames=() | |
| for file in "${audio_files[@]}"; do | |
| local basename=$(basename "$file") | |
| # Remove common audio extensions | |
| basename=${basename%.*} | |
| basenames+=("$basename") | |
| done | |
| # Look for common prefixes that could be title/author | |
| local first_name="${basenames[0]}" | |
| local common_prefix="" | |
| # Find longest common prefix across all filenames | |
| for ((i=1; i<${#first_name}; i++)); do | |
| local prefix="${first_name:0:i}" | |
| local all_match=true | |
| for name in "${basenames[@]:1}"; do | |
| if [[ "$name" != "$prefix"* ]]; then | |
| all_match=false | |
| break | |
| fi | |
| done | |
| if [[ "$all_match" == "true" ]]; then | |
| common_prefix="$prefix" | |
| else | |
| break | |
| fi | |
| done | |
| # Clean up common prefix and extract meaningful parts | |
| if [[ -n "$common_prefix" && ${#common_prefix} -gt 10 ]]; then | |
| # Remove trailing separators and numbers | |
| common_prefix=$(echo "$common_prefix" | sed -E 's/[[:space:]]*[-_]*[[:space:]]*[0-9]*[[:space:]]*$//') | |
| # Look for author - title pattern (Author - Title) | |
| if [[ "$common_prefix" =~ ^(.+)[[:space:]]*-[[:space:]]*(.+)$ ]]; then | |
| author="${BASH_REMATCH[1]}" | |
| title="${BASH_REMATCH[2]}" | |
| log_verbose "Filename pattern detected - Author: '$author', Title: '$title'" >&2 | |
| elif [[ ${#common_prefix} -gt 5 ]]; then | |
| # Use as title if long enough | |
| title="$common_prefix" | |
| log_verbose "Filename pattern detected - Title: '$title'" >&2 | |
| fi | |
| fi | |
| echo "$title|$author" | |
| } | |
| extract_metadata() { | |
| local audio_files=("$@") | |
| local first_audio="${audio_files[0]}" | |
| local book_name="$(basename "$(dirname "$first_audio")")" | |
| local book_dir="$(dirname "$first_audio")" | |
| log_verbose "Extracting metadata from: $(basename "$first_audio")" >&2 | |
| # Extract audio metadata | |
| local audio_title=$(ffprobe -v quiet -show_entries format_tags=title -of default=noprint_wrappers=1:nokey=1 "$first_audio" 2>/dev/null || echo "") | |
| local audio_artist=$(ffprobe -v quiet -show_entries format_tags=artist -of default=noprint_wrappers=1:nokey=1 "$first_audio" 2>/dev/null || echo "") | |
| local audio_album=$(ffprobe -v quiet -show_entries format_tags=album -of default=noprint_wrappers=1:nokey=1 "$first_audio" 2>/dev/null || echo "") | |
| local date=$(ffprobe -v quiet -show_entries format_tags=date -of default=noprint_wrappers=1:nokey=1 "$first_audio" 2>/dev/null || echo "") | |
| # Analyze filename patterns for multi-file books | |
| local filename_patterns=$(analyze_filename_patterns "${audio_files[@]}") | |
| IFS='|' read -r pattern_title pattern_artist <<< "$filename_patterns" | |
| # Manual overrides take highest priority | |
| local final_title="$OVERRIDE_TITLE" | |
| local final_artist="$OVERRIDE_AUTHOR" | |
| # If no manual override for title, use audio metadata and clean it | |
| if [[ -z "$final_title" ]]; then | |
| if [[ -n "$audio_title" ]] && ! echo "$audio_title" | grep -q "Chapter\|Part\|CD"; then | |
| # Clean track numbers from audio title | |
| local clean_audio_title="$audio_title" | |
| if echo "$audio_title" | grep -q "^[0-9]\{2,3\}[[:space:]]"; then | |
| clean_audio_title=$(echo "$audio_title" | sed -E 's/^[0-9]{2,3}[[:space:]]*//') | |
| log_verbose "Cleaned audio title: '$audio_title' -> '$clean_audio_title'" >&2 | |
| fi | |
| if [[ ${#clean_audio_title} -gt 10 ]]; then | |
| final_title="$clean_audio_title" | |
| log_verbose "Using cleaned audio metadata title: $final_title" >&2 | |
| elif [[ -n "$pattern_title" ]]; then | |
| final_title="$pattern_title" | |
| log_verbose "Using filename pattern title: $final_title" >&2 | |
| else | |
| final_title="$book_name" | |
| log_verbose "Using folder name as title: $final_title" >&2 | |
| fi | |
| elif [[ -n "$pattern_title" ]]; then | |
| final_title="$pattern_title" | |
| log_verbose "Using filename pattern title: $final_title" >&2 | |
| else | |
| final_title="$book_name" | |
| log_verbose "Using folder name as title: $final_title" >&2 | |
| fi | |
| else | |
| log_verbose "Using override title: $final_title" >&2 | |
| fi | |
| # If no manual override for artist, use audio metadata, then filename patterns | |
| if [[ -z "$final_artist" ]]; then | |
| if [[ -n "$audio_artist" ]]; then | |
| final_artist="$audio_artist" | |
| log_verbose "Using audio metadata artist: $final_artist" >&2 | |
| elif [[ -n "$pattern_artist" ]]; then | |
| final_artist="$pattern_artist" | |
| log_verbose "Using filename pattern artist: $final_artist" >&2 | |
| fi | |
| else | |
| log_verbose "Using override author: $final_artist" >&2 | |
| fi | |
| # Album | |
| local final_album="" | |
| if [[ -n "$audio_album" ]] && ! echo "$audio_album" | grep -q "Chapter\|Part\|CD"; then | |
| final_album="$audio_album" | |
| else | |
| final_album="$final_title" | |
| log_verbose "Using title as album: $final_album" >&2 | |
| fi | |
| echo "$final_title|$final_artist|$final_album|$date" | |
| } | |
| detect_bitrate() { | |
| local first_audio="$1" | |
| local original_bitrate=$(ffprobe -v quiet -show_entries format=bit_rate -of default=noprint_wrappers=1:nokey=1 "$first_audio" 2>/dev/null) | |
| if [[ -n "$original_bitrate" && "$original_bitrate" != "N/A" ]]; then | |
| local kbps=$((original_bitrate / 1000)) | |
| if [[ $kbps -ge 256 ]]; then | |
| echo "256k" | |
| elif [[ $kbps -ge 192 ]]; then | |
| echo "192k" | |
| elif [[ $kbps -ge 128 ]]; then | |
| echo "128k" | |
| elif [[ $kbps -ge 96 ]]; then | |
| echo "96k" | |
| else | |
| echo "64k" | |
| fi | |
| else | |
| echo "128k" | |
| fi | |
| } | |
| format_duration() { | |
| local seconds=$1 | |
| local hours=$((seconds / 3600)) | |
| local minutes=$(((seconds % 3600) / 60)) | |
| local secs=$((seconds % 60)) | |
| if [[ $hours -gt 0 ]]; then | |
| printf "%dh %dm" $hours $minutes | |
| elif [[ $minutes -gt 0 ]]; then | |
| printf "%dm %ds" $minutes $secs | |
| else | |
| printf "%ds" $secs | |
| fi | |
| } | |
| show_progress() { | |
| local current_time=$1 | |
| local total_duration=$2 | |
| if [[ $total_duration -eq 0 ]]; then | |
| return | |
| fi | |
| local percent=$((current_time * 100 / total_duration)) | |
| if [[ $percent -gt 100 ]]; then | |
| percent=100 | |
| fi | |
| local bar_width=40 | |
| local filled=$((percent * bar_width / 100)) | |
| local empty=$((bar_width - filled)) | |
| local bar="" | |
| for ((i=0; i<filled; i++)); do | |
| bar+="█" | |
| done | |
| for ((i=0; i<empty; i++)); do | |
| bar+="░" | |
| done | |
| local current_formatted=$(format_duration $current_time) | |
| local total_formatted=$(format_duration $total_duration) | |
| printf "\r[%s] %3d%% (%s / %s)" "$bar" "$percent" "$current_formatted" "$total_formatted" | |
| } | |
| run_ffmpeg_with_progress() { | |
| local total_duration=$1 | |
| shift | |
| local ffmpeg_args=("$@") | |
| if [[ "$QUIET" == "true" ]]; then | |
| ffmpeg "${ffmpeg_args[@]}" 2>/dev/null | |
| return | |
| fi | |
| # Create a temp file for ffmpeg progress | |
| local progress_file=$(mktemp) | |
| # Cleanup function | |
| cleanup_ffmpeg() { | |
| if [[ -n "$ffmpeg_pid" ]]; then | |
| kill $ffmpeg_pid 2>/dev/null | |
| wait $ffmpeg_pid 2>/dev/null | |
| fi | |
| rm -f "$progress_file" | |
| echo # New line after progress bar | |
| exit 1 | |
| } | |
| # Set up signal traps | |
| trap cleanup_ffmpeg INT TERM | |
| # Run ffmpeg in background with progress output | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| ffmpeg -y "${ffmpeg_args[@]}" & | |
| else | |
| ffmpeg -y -progress "$progress_file" "${ffmpeg_args[@]}" 2>"${progress_file}.err" & | |
| fi | |
| local ffmpeg_pid=$! | |
| # Monitor progress (only in non-verbose mode) | |
| if [[ "$VERBOSE" != "true" ]]; then | |
| local current_time=0 | |
| while kill -0 $ffmpeg_pid 2>/dev/null; do | |
| if [[ -f "$progress_file" ]]; then | |
| # Get the latest time from progress file | |
| local latest_time=$(grep "out_time_ms=" "$progress_file" 2>/dev/null | tail -1 | cut -d'=' -f2) | |
| if [[ -n "$latest_time" && "$latest_time" != "N/A" ]]; then | |
| current_time=$((latest_time / 1000000)) # Convert microseconds to seconds | |
| show_progress $current_time $total_duration | |
| fi | |
| fi | |
| sleep 0.1 # Faster polling for quick conversions | |
| done | |
| # Show final progress | |
| show_progress $total_duration $total_duration | |
| echo # New line after progress bar | |
| fi | |
| # Wait for ffmpeg to complete and get exit status | |
| wait $ffmpeg_pid | |
| local exit_status=$? | |
| # Clean up | |
| trap - INT TERM | |
| rm -f "$progress_file" "${progress_file}.err" | |
| # Check for errors if conversion failed | |
| if [[ $exit_status -ne 0 && -f "${progress_file}.err" ]]; then | |
| echo "\nConversion failed. Error details:" >&2 | |
| cat "${progress_file}.err" >&2 | |
| fi | |
| return $exit_status | |
| } | |
| create_chapter_metadata() { | |
| local audio_files=("$@") | |
| local chapter_file=$(mktemp) | |
| local start_time=0 | |
| local chapter_num=1 | |
| local total_chapters=${#audio_files[@]} | |
| local total_duration=0 | |
| if [[ "$QUIET" != "true" ]]; then | |
| echo "Creating chapter metadata (${total_chapters} chapters)..." >&2 | |
| fi | |
| echo ";FFMETADATA1" > "$chapter_file" | |
| for file in "${audio_files[@]}"; do | |
| if [[ "$QUIET" != "true" ]]; then | |
| echo "Processing chapter $chapter_num/$total_chapters: $(basename "$file")..." >&2 | |
| fi | |
| local duration=$(ffprobe -v quiet -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null) | |
| duration=$(printf "%.0f" "$duration") | |
| total_duration=$((total_duration + duration)) | |
| local filename=$(basename "$file") | |
| filename=${filename%.*} # Remove extension | |
| local chapter_title="Chapter $chapter_num" | |
| if echo "$filename" | grep -q "CD[0-9]"; then | |
| chapter_title="$filename" | |
| elif echo "$filename" | grep -q "Chapter.*[0-9]"; then | |
| chapter_title="$filename" | |
| elif echo "$filename" | grep -q "Part.*[0-9]"; then | |
| chapter_title="$filename" | |
| elif echo "$filename" | grep -q "^[0-9]"; then | |
| chapter_title="$filename" | |
| fi | |
| echo "" >> "$chapter_file" | |
| echo "[CHAPTER]" >> "$chapter_file" | |
| echo "TIMEBASE=1/1000" >> "$chapter_file" | |
| echo "START=$((start_time * 1000))" >> "$chapter_file" | |
| echo "END=$(((start_time + duration) * 1000))" >> "$chapter_file" | |
| echo "title=$chapter_title" >> "$chapter_file" | |
| start_time=$((start_time + duration)) | |
| chapter_num=$((chapter_num + 1)) | |
| done | |
| log_verbose "Chapter metadata file: $chapter_file" >&2 | |
| echo "$chapter_file|$total_duration" | |
| } | |
| process_book() { | |
| local book_dir="$1" | |
| local bitrate="$2" | |
| local chapter_time="$3" | |
| if [[ ! -d "$book_dir" ]]; then | |
| echo "Error: Directory not found: $book_dir" | |
| return 1 | |
| fi | |
| local audio_files=() | |
| while IFS= read -r -d '' file; do | |
| audio_files+=("$file") | |
| done < <(find "$book_dir" \( -name "*.mp3" -o -name "*.aac" -o -name "*.m4a" -o -name "*.mp4" -o -name "*.flac" -o -name "*.ogg" -o -name "*.wav" \) -type f -print0 | sort -z) | |
| if [[ ${#audio_files[@]} -eq 0 ]]; then | |
| echo "No audio files found in: $book_dir" | |
| return 1 | |
| fi | |
| local book_name=$(basename "$book_dir") | |
| local output_file=$(get_output_path "$book_dir" "$book_name") | |
| local output_dir=$(dirname "$output_file") | |
| if [[ ! -d "$output_dir" ]]; then | |
| log_verbose "Creating output directory: $output_dir" | |
| mkdir -p "$output_dir" | |
| fi | |
| log_info "Processing: $book_name (${#audio_files[@]} files)" | |
| local metadata=$(extract_metadata "${audio_files[@]}") | |
| IFS='|' read -r title artist album date <<< "$metadata" | |
| if [[ "$bitrate" == "copy" ]]; then | |
| bitrate=$(detect_bitrate "${audio_files[0]}") | |
| log_info "Detected bitrate: $bitrate" | |
| fi | |
| # Create chapter metadata FIRST | |
| local chapter_file | |
| local chapter_result=$(create_chapter_metadata "${audio_files[@]}") | |
| IFS='|' read -r chapter_file total_duration <<< "$chapter_result" | |
| local metadata_args=() | |
| metadata_args+=(-metadata "title=$title") | |
| [[ -n "$artist" ]] && metadata_args+=(-metadata "artist=$artist") | |
| metadata_args+=(-metadata "album=$album") | |
| [[ -n "$date" ]] && metadata_args+=(-metadata "date=$date") | |
| metadata_args+=(-metadata "genre=Audiobook") | |
| metadata_args+=(-metadata "media_type=2") | |
| log_verbose "Metadata: Title='$title', Artist='$artist', Album='$album'" | |
| local duration_formatted=$(format_duration $total_duration) | |
| log_info "Converting to M4B with chapters (${#audio_files[@]} files, $duration_formatted)..." | |
| # Check if output file exists and prompt user | |
| if [[ -f "$output_file" ]]; then | |
| echo "File '$output_file' already exists." | |
| read -p "Overwrite? [y/N] " -n 1 -r | |
| echo # New line after response | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| echo "Conversion cancelled." | |
| rm -f "$chapter_file" | |
| return 0 | |
| fi | |
| fi | |
| # Fast mode for single files - transcode audio but copy video | |
| if [[ "$FAST_MODE" == "true" || (${#audio_files[@]} -eq 1 && "$FORCE_CONCAT" != "true") ]]; then | |
| log_verbose "Using fast single-file conversion" | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| run_ffmpeg_with_progress $total_duration -v info -stats -i "${audio_files[0]}" \ | |
| -i "$chapter_file" \ | |
| -map 0:a -map 0:v? -map_metadata 1 \ | |
| -c:a aac_at -b:a "$bitrate" \ | |
| -c:v copy -disposition:v:0 attached_pic \ | |
| -movflags +faststart \ | |
| "${metadata_args[@]}" \ | |
| "$output_file" | |
| else | |
| run_ffmpeg_with_progress $total_duration -v error -i "${audio_files[0]}" \ | |
| -i "$chapter_file" \ | |
| -map 0:a -map 0:v? -map_metadata 1 \ | |
| -c:a aac_at -b:a "$bitrate" \ | |
| -c:v copy -disposition:v:0 attached_pic \ | |
| -movflags +faststart \ | |
| "${metadata_args[@]}" \ | |
| "$output_file" | |
| fi | |
| else | |
| # Multi-file conversion - extract cover art, convert audio-only, then add cover back | |
| local temp_list=$(mktemp) | |
| local cover_file="" | |
| # Extract cover art from first file if it exists | |
| if ffprobe -v quiet -select_streams v:0 -show_entries stream=index "${audio_files[0]}" 2>/dev/null | grep -q "index"; then | |
| cover_file=$(mktemp -t cover.XXXXXX).jpg | |
| ffmpeg -v quiet -i "${audio_files[0]}" -an -vcodec copy "$cover_file" 2>/dev/null || cover_file="" | |
| log_verbose "Extracted cover art to temp file" | |
| fi | |
| for file in "${audio_files[@]}"; do | |
| echo "file '$file'" >> "$temp_list" | |
| done | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| run_ffmpeg_with_progress $total_duration -v info -stats -f concat -safe 0 -i "$temp_list" \ | |
| -i "$chapter_file" \ | |
| -map 0:a -map_metadata 1 \ | |
| -c:a aac_at -b:a "$bitrate" \ | |
| -movflags +faststart \ | |
| "${metadata_args[@]}" \ | |
| "$output_file" | |
| else | |
| run_ffmpeg_with_progress $total_duration -v error -f concat -safe 0 -i "$temp_list" \ | |
| -i "$chapter_file" \ | |
| -map 0:a -map_metadata 1 \ | |
| -c:a aac_at -b:a "$bitrate" \ | |
| -movflags +faststart \ | |
| "${metadata_args[@]}" \ | |
| "$output_file" | |
| fi | |
| # Add cover art back if we extracted one | |
| if [[ -n "$cover_file" && -f "$cover_file" ]]; then | |
| local temp_output=$(mktemp -t output.XXXXXX).m4b | |
| mv "$output_file" "$temp_output" | |
| if [[ "$VERBOSE" == "true" ]]; then | |
| ffmpeg -v info -i "$temp_output" -i "$cover_file" \ | |
| -map 0:a -map 1:0 \ | |
| -c:a copy -c:v copy -disposition:v:0 attached_pic \ | |
| "$output_file" | |
| else | |
| ffmpeg -v quiet -i "$temp_output" -i "$cover_file" \ | |
| -map 0:a -map 1:0 \ | |
| -c:a copy -c:v copy -disposition:v:0 attached_pic \ | |
| "$output_file" 2>/dev/null | |
| fi | |
| rm "$temp_output" "$cover_file" | |
| log_verbose "Added cover art back to final file" | |
| fi | |
| rm "$temp_list" | |
| fi | |
| rm "$chapter_file" | |
| log_info "Created: $output_file" | |
| if [[ "$QUIET" != "true" ]]; then | |
| echo "Title: $title" | |
| [[ -n "$artist" ]] && echo "Artist: $artist" | |
| echo "Chapters: ${#audio_files[@]}" | |
| echo "" | |
| fi | |
| } | |
| # Parse arguments | |
| BITRATE="$DEFAULT_BITRATE" | |
| CHAPTER_TIME="$DEFAULT_CHAPTER_TIME" | |
| TARGET_PATH="" | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| -b|--bitrate) | |
| BITRATE="$2" | |
| shift 2 | |
| ;; | |
| -c|--chapter-time) | |
| CHAPTER_TIME="$2" | |
| shift 2 | |
| ;; | |
| -o|--output-dir) | |
| OUTPUT_DIR="$2" | |
| OUTPUT_TO_PARENT=false | |
| shift 2 | |
| ;; | |
| -p|--parent) | |
| OUTPUT_TO_PARENT=true | |
| OUTPUT_DIR="" | |
| shift | |
| ;; | |
| -s|--same-dir) | |
| OUTPUT_TO_PARENT=false | |
| OUTPUT_DIR="" | |
| shift | |
| ;; | |
| -a|--author) | |
| OVERRIDE_AUTHOR="$2" | |
| shift 2 | |
| ;; | |
| -t|--title) | |
| OVERRIDE_TITLE="$2" | |
| shift 2 | |
| ;; | |
| --no-lookup) | |
| NO_LOOKUP=true | |
| shift | |
| ;; | |
| --fast) | |
| FAST_MODE=true | |
| shift | |
| ;; | |
| --force-concat) | |
| FORCE_CONCAT=true | |
| shift | |
| ;; | |
| -v|--verbose) | |
| VERBOSE=true | |
| shift | |
| ;; | |
| -q|--quiet) | |
| QUIET=true | |
| shift | |
| ;; | |
| -h|--help) | |
| show_help | |
| exit 0 | |
| ;; | |
| -*) | |
| echo "Unknown option: $1" | |
| show_help | |
| exit 1 | |
| ;; | |
| *) | |
| TARGET_PATH="$1" | |
| shift | |
| ;; | |
| esac | |
| done | |
| if [[ -z "$TARGET_PATH" ]]; then | |
| echo "Error: Please specify a path" | |
| show_help | |
| exit 1 | |
| fi | |
| check_dependencies | |
| TARGET_PATH=$(realpath "$TARGET_PATH") | |
| if [[ -n "$OUTPUT_DIR" ]]; then | |
| OUTPUT_DIR=$(realpath "$OUTPUT_DIR") | |
| if [[ ! -d "$OUTPUT_DIR" ]]; then | |
| log_info "Creating output directory: $OUTPUT_DIR" | |
| mkdir -p "$OUTPUT_DIR" | |
| fi | |
| fi | |
| if [[ -d "$TARGET_PATH" ]]; then | |
| if find "$TARGET_PATH" -maxdepth 1 \( -name "*.mp3" -o -name "*.aac" -o -name "*.m4a" -o -name "*.mp4" -o -name "*.flac" -o -name "*.ogg" -o -name "*.wav" \) -type f | head -1 | grep -q .; then | |
| process_book "$TARGET_PATH" "$BITRATE" "$CHAPTER_TIME" | |
| else | |
| for book_dir in "$TARGET_PATH"/*/; do | |
| if [[ -d "$book_dir" ]]; then | |
| process_book "$book_dir" "$BITRATE" "$CHAPTER_TIME" | |
| fi | |
| done | |
| fi | |
| else | |
| echo "Error: Not a directory: $TARGET_PATH" | |
| exit 1 | |
| fi | |
| echo "All done!" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment