Skip to content

Instantly share code, notes, and snippets.

@djvs
Last active December 12, 2020 22:11
Show Gist options
  • Save djvs/51c7712d8bba36a1ba23e2df23c3fc77 to your computer and use it in GitHub Desktop.
Save djvs/51c7712d8bba36a1ba23e2df23c3fc77 to your computer and use it in GitHub Desktop.
Script to break down YouTube playlist audio into separate files per-track, based on YouTube description/comment track lists. Depends on commandline ffmpeg
#!/usr/bin/env ruby
require 'shellwords'
require 'open3'
require 'pry-rails'
if ARGV.length < 2
case ARGV[0]
when nil, "--help"
puts "Usage: youtube-playlist-split.rb AUDIO_FILE PLAYLIST_FILE
Will break apart a video or audio file in its current directory, according to track start times specified in PLAYLIST_FILE (in typical YouTube format)
Options:
--help Print this help
--example-playlist Show example playlist
"
when '--example-playlist'
puts "example playlist:
1. Artist - Song 12:24
2. My Artist - My Song 42:20
3. Other Artist - Your Song 01:12:20"
end
exit
end
source = File.expand_path(ARGV[0])
pl = File.expand_path(ARGV[1])
ext = source.split('.').last
dir = source.split('/')[0..-2].join('/') + "/"
duration_str = Open3.capture3("ffmpeg -i #{Shellwords.escape(source)}")[1].match(/Duration: [\d:]*/).to_s.gsub("Duration: ","")
manifest = {}
trackstrs = File.read(pl).split("\n")
trackstrs.each do |x|
filename = x.gsub(/[\d:]*$/, '').strip
manifest[filename.to_sym] = {
start_time: x.match(/[\d:]+:[\d:]+/).to_s
}
end
def parsetime(str)
str.split(':').map(&:to_i).inject(0) { |a, b| a * 60 + b }
end
mks = manifest.keys
mks.each_with_index do |k,i|
next_item = manifest[mks[i+1]]
if next_item
next_str = next_item[:start_time]
else
next_str = duration_str
end
manifest[k][:end_time] = next_str
manifest[k][:len] = parsetime(manifest[k][:end_time]) - parsetime(manifest[k][:start_time])
filename = k.to_s.gsub(manifest[k][:start_time], '').strip
# binding.pry
if !filename.to_s.match(/^[\d]+/)
filename = "#{i.to_s.rjust(2, "0")} #{filename}"
end
filename = "#{Shellwords.escape(dir)}#{Shellwords.escape(filename)}.#{ext}"
manifest[k][:filename] = filename
end
puts "Parsed track list:"
manifest.each do |k,v|
puts "Filename: #{v[:filename]}"
puts "Start time: #{v[:start_time]} End time: #{v[:end_time]} Length: #{v[:len]}"
end
puts "Proceed? Y/n"
answer = $stdin.gets.chomp
exit if answer.downcase == "n"
manifest.each.with_index do |x,i|
# k = x[0]
v = x[1]
cmd = "ffmpeg -i #{Shellwords.escape(source)} -ss #{Shellwords.escape(v[:start_time])} -t #{Shellwords.escape(v[:len])} -c:a copy #{v[:filename]}"
# binding.pry
puts "Executing: " + cmd
puts `#{cmd}`
puts
puts "-----------------------------------------------------"
puts
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment