change opus enc
This commit is contained in:
@@ -1,21 +1,35 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Media Library FLAC to Opus Conversion Script
|
# Media Library FLAC to Opus Conversion Script
|
||||||
# Processes multiple albums in a media library, converting all FLAC files to Opus 448kbps
|
# Uses opus-tools (opusenc) for high-quality FLAC to Opus conversion
|
||||||
# Also copies cover art files to maintain album artwork
|
# Also copies cover art files to maintain album artwork
|
||||||
|
|
||||||
# Check if ffmpeg is installed
|
# Check if required tools are installed
|
||||||
if ! command -v ffmpeg &> /dev/null; then
|
check_dependencies() {
|
||||||
echo "Error: ffmpeg is not installed or not in PATH"
|
local missing_tools=()
|
||||||
exit 1
|
|
||||||
|
if ! command -v opusenc &> /dev/null; then
|
||||||
|
missing_tools+=("opusenc (from opus-tools package)")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if ffmpeg has opus encoder support
|
if ! command -v flac &> /dev/null; then
|
||||||
if ! ffmpeg -encoders 2>/dev/null | grep -q "libopus"; then
|
missing_tools+=("flac (for metadata extraction)")
|
||||||
echo "Error: ffmpeg does not have Opus encoder support (libopus)"
|
fi
|
||||||
echo "Please install ffmpeg with Opus support"
|
|
||||||
|
if [ ${#missing_tools[@]} -gt 0 ]; then
|
||||||
|
echo "Error: Missing required tools:"
|
||||||
|
for tool in "${missing_tools[@]}"; do
|
||||||
|
echo " - $tool"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "To install missing dependencies:"
|
||||||
|
echo " Ubuntu/Debian: sudo apt install opus-tools flac"
|
||||||
|
echo " macOS: brew install opus-tools flac"
|
||||||
|
echo " Arch Linux: sudo pacman -S opus-tools flac"
|
||||||
|
echo " Fedora: sudo dnf install opus-tools flac"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to display usage
|
# Function to display usage
|
||||||
show_usage() {
|
show_usage() {
|
||||||
@@ -27,8 +41,10 @@ show_usage() {
|
|||||||
echo " -p, --preserve Create Opus files alongside original FLAC files"
|
echo " -p, --preserve Create Opus files alongside original FLAC files"
|
||||||
echo " -t, --test Test mode - show what would be processed"
|
echo " -t, --test Test mode - show what would be processed"
|
||||||
echo " -f, --force Force re-conversion of existing Opus files"
|
echo " -f, --force Force re-conversion of existing Opus files"
|
||||||
echo " -b, --bitrate RATE Opus bitrate in kbps (default: 448)"
|
echo " -b, --bitrate RATE Opus bitrate in kbps (default: 192)"
|
||||||
|
echo " -q, --quality LEVEL Use VBR quality instead of bitrate (0-10, 10=best)"
|
||||||
echo " --no-covers Skip copying cover art files"
|
echo " --no-covers Skip copying cover art files"
|
||||||
|
echo " --no-metadata Skip copying metadata from FLAC files"
|
||||||
echo " -h, --help Show this help message"
|
echo " -h, --help Show this help message"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
@@ -36,7 +52,8 @@ show_usage() {
|
|||||||
echo " $0 -o /path/to/output /path/to/music/library"
|
echo " $0 -o /path/to/output /path/to/music/library"
|
||||||
echo " $0 --test /path/to/music/library"
|
echo " $0 --test /path/to/music/library"
|
||||||
echo " $0 --replace /path/to/music/library"
|
echo " $0 --replace /path/to/music/library"
|
||||||
echo " $0 -b 384 /path/to/music/library"
|
echo " $0 -b 256 /path/to/music/library"
|
||||||
|
echo " $0 -q 8 /path/to/music/library # VBR quality 8 (very good) with .opus.ogg extension"
|
||||||
echo " $0 --no-covers /path/to/music/library"
|
echo " $0 --no-covers /path/to/music/library"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +64,10 @@ PRESERVE_MODE=false
|
|||||||
TEST_MODE=false
|
TEST_MODE=false
|
||||||
FORCE_MODE=false
|
FORCE_MODE=false
|
||||||
COPY_COVERS=true
|
COPY_COVERS=true
|
||||||
|
COPY_METADATA=true
|
||||||
OPUS_BITRATE=192
|
OPUS_BITRATE=192
|
||||||
|
OPUS_QUALITY=""
|
||||||
|
USE_VBR_QUALITY=false
|
||||||
PROCESSED_COUNT=0
|
PROCESSED_COUNT=0
|
||||||
SKIPPED_COUNT=0
|
SKIPPED_COUNT=0
|
||||||
ERROR_COUNT=0
|
ERROR_COUNT=0
|
||||||
@@ -87,12 +107,22 @@ while [[ $# -gt 0 ]]; do
|
|||||||
;;
|
;;
|
||||||
-b|--bitrate)
|
-b|--bitrate)
|
||||||
OPUS_BITRATE="$2"
|
OPUS_BITRATE="$2"
|
||||||
|
USE_VBR_QUALITY=false
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-q|--quality)
|
||||||
|
OPUS_QUALITY="$2"
|
||||||
|
USE_VBR_QUALITY=true
|
||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--no-covers)
|
--no-covers)
|
||||||
COPY_COVERS=false
|
COPY_COVERS=false
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--no-metadata)
|
||||||
|
COPY_METADATA=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
show_usage
|
show_usage
|
||||||
exit 0
|
exit 0
|
||||||
@@ -109,11 +139,23 @@ while [[ $# -gt 0 ]]; do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# Validate bitrate
|
# Check dependencies
|
||||||
if ! [[ "$OPUS_BITRATE" =~ ^[0-9]+$ ]] || [ "$OPUS_BITRATE" -lt 64 ] || [ "$OPUS_BITRATE" -gt 512 ]; then
|
check_dependencies
|
||||||
echo "Error: Invalid bitrate '$OPUS_BITRATE'. Must be between 64 and 512 kbps"
|
|
||||||
|
# Validate quality or bitrate settings
|
||||||
|
if [ "$USE_VBR_QUALITY" = true ]; then
|
||||||
|
if ! [[ "$OPUS_QUALITY" =~ ^[0-9]+$ ]] || [ "$OPUS_QUALITY" -lt 0 ] || [ "$OPUS_QUALITY" -gt 10 ]; then
|
||||||
|
echo "Error: Invalid quality level '$OPUS_QUALITY'. Must be between 0 and 10"
|
||||||
|
echo "Quality levels: 0 (lowest), 5 (good), 8 (very good), 10 (best)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
if ! [[ "$OPUS_BITRATE" =~ ^[0-9]+$ ]] || [ "$OPUS_BITRATE" -lt 6 ] || [ "$OPUS_BITRATE" -gt 510 ]; then
|
||||||
|
echo "Error: Invalid bitrate '$OPUS_BITRATE'. Must be between 6 and 510 kbps"
|
||||||
|
echo "Recommended values: 96 (good), 128 (very good), 192 (excellent), 256 (overkill)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if media library path is provided
|
# Check if media library path is provided
|
||||||
if [ -z "$MEDIA_LIBRARY" ]; then
|
if [ -z "$MEDIA_LIBRARY" ]; then
|
||||||
@@ -157,6 +199,77 @@ needs_conversion() {
|
|||||||
return 0 # Needs conversion
|
return 0 # Needs conversion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Function to extract embedded cover art from FLAC file
|
||||||
|
extract_flac_cover_art() {
|
||||||
|
local flac_file="$1"
|
||||||
|
local temp_cover_file="$2"
|
||||||
|
|
||||||
|
if [ "$COPY_COVERS" = false ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to extract embedded cover art using metaflac
|
||||||
|
if command -v metaflac &> /dev/null; then
|
||||||
|
# Check if the FLAC file has embedded pictures
|
||||||
|
if metaflac --list --block-type=PICTURE "$flac_file" 2>/dev/null | grep -q "PICTURE"; then
|
||||||
|
# Extract the first picture (cover art)
|
||||||
|
if metaflac --export-picture-to="$temp_cover_file" "$flac_file" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: try ffmpeg to extract embedded art
|
||||||
|
if command -v ffmpeg &> /dev/null; then
|
||||||
|
if ffmpeg -hide_banner -y -i "$flac_file" -an -vcodec copy "$temp_cover_file" 2>/dev/null; then
|
||||||
|
# Check if the extracted file has content
|
||||||
|
if [ -s "$temp_cover_file" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to extract metadata from FLAC file
|
||||||
|
extract_flac_metadata() {
|
||||||
|
local flac_file="$1"
|
||||||
|
local temp_tags_file="$2"
|
||||||
|
|
||||||
|
if [ "$COPY_METADATA" = false ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract metadata using metaflac (preferred method)
|
||||||
|
if command -v metaflac &> /dev/null; then
|
||||||
|
metaflac --export-tags-to="$temp_tags_file" "$flac_file" 2>/dev/null
|
||||||
|
|
||||||
|
# If metaflac succeeded and file has content, we're done
|
||||||
|
if [ -s "$temp_tags_file" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fallback: use ffprobe if available (often more comprehensive)
|
||||||
|
if command -v ffprobe &> /dev/null; then
|
||||||
|
ffprobe -v quiet -show_entries format_tags -of csv=p=0:s=x "$flac_file" 2>/dev/null | \
|
||||||
|
sed 's/^tag://; s/x/\n/g' | grep '=' > "$temp_tags_file"
|
||||||
|
|
||||||
|
# If ffprobe succeeded and file has content, we're done
|
||||||
|
if [ -s "$temp_tags_file" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Last fallback: use flac command to list tags
|
||||||
|
if command -v flac &> /dev/null; then
|
||||||
|
flac --list --block-type=VORBIS_COMMENT "$flac_file" 2>/dev/null | \
|
||||||
|
grep -E "^\s+comment\[[0-9]+\]:" | \
|
||||||
|
sed 's/^\s*comment\[[0-9]*\]: //' > "$temp_tags_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to copy cover art files
|
# Function to copy cover art files
|
||||||
copy_cover_art() {
|
copy_cover_art() {
|
||||||
local source_dir="$1"
|
local source_dir="$1"
|
||||||
@@ -214,17 +327,17 @@ process_flac_file() {
|
|||||||
local relative_path="$2"
|
local relative_path="$2"
|
||||||
local output_file=""
|
local output_file=""
|
||||||
|
|
||||||
# Determine output file path
|
# Determine output file path with .opus.ogg extension
|
||||||
if [ "$REPLACE_MODE" = true ]; then
|
if [ "$REPLACE_MODE" = true ]; then
|
||||||
# Replace .flac with .ogg in same location
|
# Replace .flac with .opus.ogg in same location
|
||||||
output_file="${input_file%.flac}.ogg"
|
output_file="${input_file%.flac}.opus.ogg"
|
||||||
elif [ "$PRESERVE_MODE" = true ]; then
|
elif [ "$PRESERVE_MODE" = true ]; then
|
||||||
local dir=$(dirname "$input_file")
|
local dir=$(dirname "$input_file")
|
||||||
local filename=$(basename "$input_file" .flac)
|
local filename=$(basename "$input_file" .flac)
|
||||||
output_file="$dir/${filename}.ogg"
|
output_file="$dir/${filename}.opus.ogg"
|
||||||
else
|
else
|
||||||
# Change extension from .flac to .ogg
|
# Change extension from .flac to .opus.ogg
|
||||||
local opus_relative_path="${relative_path%.flac}.ogg"
|
local opus_relative_path="${relative_path%.flac}.opus.ogg"
|
||||||
output_file="$OUTPUT_DIR/$opus_relative_path"
|
output_file="$OUTPUT_DIR/$opus_relative_path"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -236,7 +349,11 @@ process_flac_file() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$TEST_MODE" = true ]; then
|
if [ "$TEST_MODE" = true ]; then
|
||||||
|
if [ "$USE_VBR_QUALITY" = true ]; then
|
||||||
|
echo " WOULD CONVERT: $relative_path → $(basename "$output_file") @ VBR quality ${OPUS_QUALITY}"
|
||||||
|
else
|
||||||
echo " WOULD CONVERT: $relative_path → $(basename "$output_file") @ ${OPUS_BITRATE}kbps"
|
echo " WOULD CONVERT: $relative_path → $(basename "$output_file") @ ${OPUS_BITRATE}kbps"
|
||||||
|
fi
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -264,19 +381,75 @@ process_flac_file() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create temporary file for safe processing
|
# Create temporary files
|
||||||
local temp_file="${output_file}.tmp.ogg"
|
local temp_file="${output_file}.tmp"
|
||||||
|
local temp_cover_file="${output_file}.cover.tmp"
|
||||||
|
|
||||||
|
# Extract embedded cover art from FLAC file
|
||||||
|
local has_embedded_cover=false
|
||||||
|
if extract_flac_cover_art "$input_file" "$temp_cover_file"; then
|
||||||
|
has_embedded_cover=true
|
||||||
|
echo " Found embedded cover art"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Look for external cover art if no embedded cover found
|
||||||
|
local external_cover_file=""
|
||||||
|
if [ "$has_embedded_cover" = false ]; then
|
||||||
|
local source_dir=$(dirname "$input_file")
|
||||||
|
for cover_name in "${COVER_FILENAMES[@]}"; do
|
||||||
|
local cover_file="$source_dir/$cover_name"
|
||||||
|
if [ -f "$cover_file" ]; then
|
||||||
|
external_cover_file="$cover_file"
|
||||||
|
echo " Found external cover art: $cover_name"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build opusenc command
|
||||||
|
local opusenc_cmd=(opusenc)
|
||||||
|
|
||||||
|
# Add quality or bitrate settings
|
||||||
|
if [ "$USE_VBR_QUALITY" = true ]; then
|
||||||
|
opusenc_cmd+=(--vbr --comp "$OPUS_QUALITY")
|
||||||
|
echo " CONVERTING: $relative_path → Opus VBR quality ${OPUS_QUALITY}"
|
||||||
|
else
|
||||||
|
opusenc_cmd+=(--bitrate "$OPUS_BITRATE")
|
||||||
echo " CONVERTING: $relative_path → Opus ${OPUS_BITRATE}kbps"
|
echo " CONVERTING: $relative_path → Opus ${OPUS_BITRATE}kbps"
|
||||||
|
fi
|
||||||
|
|
||||||
# Convert the file to Opus
|
# Add embedded cover art if available
|
||||||
echo " Command: ffmpeg -i \"$input_file\" -c:a libopus -b:a ${OPUS_BITRATE}k -vbr on -compression_level 10 -y \"$temp_file\""
|
if [ "$has_embedded_cover" = true ] && [ -f "$temp_cover_file" ]; then
|
||||||
|
opusenc_cmd+=(--picture "$temp_cover_file")
|
||||||
|
echo " Embedding cover art from FLAC"
|
||||||
|
elif [ -n "$external_cover_file" ]; then
|
||||||
|
opusenc_cmd+=(--picture "$external_cover_file")
|
||||||
|
echo " Embedding external cover art"
|
||||||
|
fi
|
||||||
|
|
||||||
if ffmpeg -i "$input_file" -c:a libopus -b:a "${OPUS_BITRATE}k" -vbr on -compression_level 10 -y "$temp_file" 2>"${temp_file}.log"; then
|
# Note: opusenc automatically copies metadata from FLAC files
|
||||||
|
# Only add manual metadata if we need to override or add specific tags
|
||||||
|
if [ "$COPY_METADATA" = true ]; then
|
||||||
|
echo " Metadata: opusenc will automatically copy FLAC tags"
|
||||||
|
else
|
||||||
|
# If metadata copying is disabled, use --discard-comments
|
||||||
|
opusenc_cmd+=(--discard-comments)
|
||||||
|
echo " Metadata: Disabled (using --discard-comments)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add input and output files
|
||||||
|
opusenc_cmd+=("$input_file" "$temp_file")
|
||||||
|
|
||||||
|
echo " Command: ${opusenc_cmd[*]}"
|
||||||
|
|
||||||
|
# Run opusenc with error logging
|
||||||
|
if "${opusenc_cmd[@]}" 2>"${temp_file}.log"; then
|
||||||
|
# Verify the output file was created and has content
|
||||||
|
if [ -s "$temp_file" ]; then
|
||||||
# Move temp file to final location
|
# Move temp file to final location
|
||||||
mv "$temp_file" "$output_file"
|
mv "$temp_file" "$output_file"
|
||||||
echo " ✓ DONE: $(basename "$output_file")"
|
echo " ✓ DONE: $(basename "$output_file")"
|
||||||
rm -f "${temp_file}.log"
|
rm -f "${temp_file}.log" "$temp_cover_file"
|
||||||
((PROCESSED_COUNT++))
|
((PROCESSED_COUNT++))
|
||||||
|
|
||||||
# If replace mode, remove original FLAC file
|
# If replace mode, remove original FLAC file
|
||||||
@@ -284,14 +457,19 @@ process_flac_file() {
|
|||||||
rm "$input_file"
|
rm "$input_file"
|
||||||
echo " Removed original: $(basename "$input_file")"
|
echo " Removed original: $(basename "$input_file")"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo " ✗ ERROR: Output file is empty or wasn't created properly"
|
||||||
|
rm -f "$temp_file" "$temp_cover_file"
|
||||||
|
((ERROR_COUNT++))
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo " ✗ ERROR: Failed to convert $relative_path"
|
echo " ✗ ERROR: Failed to convert $relative_path"
|
||||||
echo " Error log saved to: ${temp_file}.log"
|
echo " Error log saved to: ${temp_file}.log"
|
||||||
if [ -f "${temp_file}.log" ]; then
|
if [ -f "${temp_file}.log" ]; then
|
||||||
echo " Last few lines of error:"
|
echo " Error details:"
|
||||||
tail -n 5 "${temp_file}.log" | sed 's/^/ /'
|
tail -n 5 "${temp_file}.log" | sed 's/^/ /'
|
||||||
fi
|
fi
|
||||||
rm -f "$temp_file"
|
rm -f "$temp_file" "$temp_cover_file"
|
||||||
((ERROR_COUNT++))
|
((ERROR_COUNT++))
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -330,11 +508,22 @@ process_album() {
|
|||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Test opusenc with version info
|
||||||
|
echo "Testing opus-tools..."
|
||||||
|
opusenc_version=$(opusenc --version 2>&1 | head -n1)
|
||||||
|
echo "Found: $opusenc_version"
|
||||||
|
|
||||||
# Main processing
|
# Main processing
|
||||||
echo "Media Library FLAC to Opus Converter"
|
echo ""
|
||||||
echo "===================================="
|
echo "Media Library FLAC to Opus Converter (using opus-tools)"
|
||||||
|
echo "======================================================="
|
||||||
echo "Input: $MEDIA_LIBRARY"
|
echo "Input: $MEDIA_LIBRARY"
|
||||||
echo "Bitrate: ${OPUS_BITRATE}kbps"
|
|
||||||
|
if [ "$USE_VBR_QUALITY" = true ]; then
|
||||||
|
echo "Quality: VBR level ${OPUS_QUALITY} (0=lowest, 10=best)"
|
||||||
|
else
|
||||||
|
echo "Bitrate: ${OPUS_BITRATE}kbps CBR"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$TEST_MODE" = true ]; then
|
if [ "$TEST_MODE" = true ]; then
|
||||||
echo "Mode: TEST MODE (no files will be modified)"
|
echo "Mode: TEST MODE (no files will be modified)"
|
||||||
@@ -351,11 +540,17 @@ if [ "$FORCE_MODE" = true ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$COPY_COVERS" = true ]; then
|
if [ "$COPY_COVERS" = true ]; then
|
||||||
echo "Covers: Enabled (will copy album artwork)"
|
echo "Covers: Enabled (will embed cover art in Opus files and copy external files)"
|
||||||
else
|
else
|
||||||
echo "Covers: Disabled"
|
echo "Covers: Disabled"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$COPY_METADATA" = true ]; then
|
||||||
|
echo "Metadata: Enabled (will copy tags from FLAC files)"
|
||||||
|
else
|
||||||
|
echo "Metadata: Disabled"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Find all directories that contain FLAC files (potential albums)
|
# Find all directories that contain FLAC files (potential albums)
|
||||||
@@ -392,5 +587,7 @@ if [ "$COPY_COVERS" = true ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ERROR_COUNT" -gt 0 ]; then
|
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "Some files failed to convert. Check the error logs for details."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
Reference in New Issue
Block a user