Last active
May 17, 2024 00:50
-
-
Save dkam/67c2670a9a81cb117255464476062da0 to your computer and use it in GitHub Desktop.
Convert a bunch of mp3 files into an m4b file
This file contains 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
#!/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