Skip to content

Instantly share code, notes, and snippets.

@dkam
Last active May 17, 2024 00:50
Show Gist options
  • Save dkam/67c2670a9a81cb117255464476062da0 to your computer and use it in GitHub Desktop.
Save dkam/67c2670a9a81cb117255464476062da0 to your computer and use it in GitHub Desktop.
Convert a bunch of mp3 files into an m4b file
#!/usr/bin/env ruby
require 'open3'
require 'tempfile'
##
# Create a single m4b file from multiple mp3 files.
# Finds all *.mp3 files, merges them, in lexigraphical order, creates chapters based on the mp3 files, adds cover.jpg as a cover
## Todo
# Add copy metadata from the mp3s - book title / author etc.
# Can those ffmpeg commands be merged into few commands?
# Function to get the duration of an MP3 file in seconds
def get_duration(file)
puts "Starting with #{file}"
stdout, stderr, status = Open3.capture3("ffmpeg -i \"#{file}\" 2>&1")
duration_line = stdout.match(/Duration: (\d+):(\d+):(\d+\.\d+)/)
hours, minutes, seconds = duration_line.captures.map(&:to_f)
(hours * 3600) + (minutes * 60) + seconds
end
# Function to format time in HH:MM:SS.mmm
def format_time(seconds)
hours = (seconds / 3600).to_i
minutes = ((seconds % 3600) / 60).to_i
seconds = (seconds % 60).to_i
milliseconds = ((seconds % 1) * 1000).to_i
format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds)
end
def generate_filelist_and_chapters
# Initialize variables
start_time = 0
chapter_number = 1
# Open filelist.txt for writing
File.open("filelist.txt", "w") do |filelist|
# Open chapters.txt for writing
File.open("chapters.txt", "w") do |chapters|
# Loop through each MP3 file
chapters.puts ";FFMETADATA1"
Dir.glob("*.mp3").sort.each do |mp3_file|
# Get the duration of the current file
duration = get_duration(mp3_file)
# Format start time
formatted_start_time = format_time(start_time)
chapters.puts "[CHAPTER]"
chapters.puts "TIMEBASE=1/1000"
chapters.puts "START=#{(start_time * 1000).to_i}"
chapters.puts "END=#{((start_time + duration) * 1000).to_i}"
chapters.puts "title=Chapter #{chapter_number}"
# Update start time and chapter number
start_time += duration
chapter_number += 1
filelist.puts "file '#{mp3_file}'"
end
end
end
end
def with_tempfile(ext1, ext2, ext3)
Tempfile.create(['tmp1', ext1]) do |tempfile1|
Tempfile.create(['tmp2', ext2]) do |tempfile2|
Tempfile.create(['tmp3', ext3]) do |tempfile3|
yield(tempfile1.path, tempfile2.path, tempfile3.path)
end
end
end
end
generate_filelist_and_chapters
with_tempfile('.m4a', '.m4b', '.m4b') do |tmpfile1, tmpfile2, tmpfile3|
system("ffmpeg -y -f concat -safe 0 -i filelist.txt -map 0:a #{tmpfile1}")
system("ffmpeg -y -i #{tmpfile1} -codec copy #{tmpfile2}")
system("ffmpeg -y -i #{tmpfile2} -i chapters.txt -map_metadata 1 -map_chapters 1 -codec copy #{tmpfile3}")
system("ffmpeg -y -i #{tmpfile3} -i cover.jpg -c copy -disposition:v attached_pic book.m4b")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment