Created
April 19, 2025 03:59
-
-
Save FNGarvin/517fadca0e629067eddf62afd30c3ead to your computer and use it in GitHub Desktop.
A Simple Script to Convert a Video Into a Collection of MP3s, One File Per Chapter
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
#!/usr/bin/ruby -w | |
#a simple script to take a video file and convert it | |
#into a collection of mp3 files, one file per chapter | |
#requires an ffmpeg install with ffprobe and support for | |
#mp3 plus whatever formats the input uses | |
#FNG, 2025 | |
require 'json' | |
require 'shellwords' # Todo: safely handle chapter titles with spaces and special characters, replace sanitize | |
class Chapter | |
attr_reader :id, :title, :start_time, :end_time, :duration | |
def initialize(id, title, start_time, end_time) | |
@id = id | |
@title = title | |
@start_time = start_time.to_f | |
@end_time = end_time.to_f | |
@duration = @end_time - @start_time | |
end | |
def to_s | |
"ID: #{@id}, Title: #{@title}, Start: #{@start_time}, End: #{@end_time}, Duration: #{@duration}" | |
end | |
end | |
def extract_chapters_from_video(video_filename) | |
begin | |
ffprobe_command = "ffprobe -v quiet -print_format json -show_chapters \"#{video_filename}\"" | |
json_output = `#{ffprobe_command}` | |
data = JSON.parse(json_output) | |
chapters_data = data['chapters'] || [] | |
chapter_objects = [] | |
chapters_data.each do |chapter_info| | |
id = chapter_info['id'] | |
title = chapter_info.dig('tags', 'title') | |
start_time = chapter_info['start_time'] | |
end_time = chapter_info['end_time'] | |
chapter_objects << Chapter.new(id, title, start_time, end_time) | |
end | |
chapter_objects | |
rescue Errno::ENOENT | |
puts "Error: File '#{video_filename}' not found." | |
return [] | |
rescue JSON::ParserError | |
puts "Error: Could not parse ffprobe output. Make sure ffmpeg/ffprobe is installed and the video file is valid." | |
return [] | |
rescue StandardError => e | |
puts "An unexpected error occurred: #{e.message}" | |
return [] | |
end | |
end | |
def sanitize_filename(title) | |
title.gsub(/[^a-zA-Z0-9\s_-]/, '') | |
end | |
def convert_chapter_to_mp3(video_filename, chapter) | |
sanitized_title = sanitize_filename(chapter.title) | |
output_filename = "[#{chapter.id}] #{sanitized_title}.mp3" | |
ffmpeg_command = "ffmpeg -i \"#{video_filename}\" -ss #{chapter.start_time} -t #{chapter.duration} -vn \"#{output_filename}\"" | |
puts "Converting chapter: #{chapter.title} (Start: #{chapter.start_time}, Duration: #{chapter.duration} seconds) to #{output_filename}" | |
system(ffmpeg_command) | |
end | |
if __FILE__ == $0 | |
if ARGV.empty? | |
puts "Usage: ruby #{$0} <video_filename>" | |
else | |
video_filename = ARGV[0] | |
chapters = extract_chapters_from_video(video_filename) | |
chapters.each do |chapter| | |
convert_chapter_to_mp3(video_filename, chapter) | |
end | |
puts "Finished processing all chapters." | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment