vidsampler – extract audio samples from online video
#!/usr/bin/env ruby | |
# | |
# vidsampler – extract audio samples from online video | |
# | |
# for OSX only | |
# | |
# Usage: | |
# | |
# ruby vidsampler.rb [youtube url] [minute:second] [duration] | |
# | |
# ex. | |
# | |
# ruby vidsampler.rb 'http://www.youtube.com/watch?v=LBRy4HfDuxc' 0:20 5 | |
# | |
# will output | |
# | |
# LBRy4HfDuxc.mp3 | |
# | |
# which is a 5 second, 256 constant bitrate mp3 in the current directory | |
# | |
# if you don't have lame installed, only wav files will be outputted | |
# http://wiki.audacityteam.org/index.php?title=Lame_Installation#Mac_Instructions | |
# | |
# options: | |
# | |
# -c [num] - a constant bitrate value for the mp3 (eg 320) | |
# -V [num] - a variable bitrate value for the mp3 (eg 1) | |
# -o [filename] - an output filename/path. if the filename has a .wav extension, | |
# a wav file will be outputted and any bitrate settings disregarded | |
# | |
# | |
END { | |
if __FILE__ == $0 | |
options = {} | |
OptionParser.new do |opts| | |
opts.banner = "Usage: sampletube.rb [youtube url] [minute:second] [duration] [options]" | |
opts.on("-b", "--cbr NUM", Integer, "constant bitrate") do |rate| | |
options[:bitrate_type] = :constant | |
options[:bitrate] = rate | |
end | |
opts.on("-o", "--output-file NAME", String, "output file") do |file| | |
options[:output_file] = file | |
end | |
opts.on("-V", "--vbr NUM", Integer, "variable bitrate") do |rate| | |
options[:bitrate_type] = :variable | |
options[:bitrate] = rate | |
end | |
end.parse! | |
url, start_time, duration = *ARGV | |
output = SampleGenerator.generate({ | |
:source_file => VideoDownloader.download(url).download_path, | |
:start_time => start_time, | |
:duration => duration | |
}.merge(options)) | |
puts "Sample: #{output}" | |
end | |
} | |
require "optparse" | |
require "uri" | |
class CommandLineUtility | |
def self.path | |
new.path | |
end | |
def initialize | |
install unless installed? | |
end | |
end | |
class YoutubeDLCommandLineUtility < CommandLineUtility | |
INSTALL_PATH = "/tmp/youtube-dl" | |
INSTALL_URL = 'https://raw.github.com/rg3/youtube-dl/2012.02.27/youtube-dl' | |
def installed? | |
File.exists?(INSTALL_PATH) || (%x|which youtube-dl| && $?.success?) | |
end | |
def path | |
if File.exists?(INSTALL_PATH) | |
INSTALL_PATH | |
else | |
%x|which youtube-dl|.chomp | |
end | |
end | |
def install | |
%x|curl '#{INSTALL_URL}' > #{INSTALL_PATH}| | |
%x|chmod +x #{INSTALL_PATH}| | |
end | |
end | |
class MplayerCommandLineUtility < CommandLineUtility | |
INSTALL_URL = 'http://mplayerosxext.googlecode.com/files/MPlayer-OSX-Extended_rev14.zip' | |
DOWNLOAD_PATH = '/tmp/mplayer.zip' | |
EXECUTABLE = "/tmp/MPlayer OSX Extended.app/Contents/" + | |
"Resources/Binaries/mpextended.mpBinaries/" + | |
"Contents/mpextended.mpBinaries/Contents/MacOS/mplayer" | |
def installed? | |
File.exists?(EXECUTABLE) || (%x|which mplayer| && $?.success?) | |
end | |
def path | |
if File.exists?(EXECUTABLE) | |
EXECUTABLE | |
else | |
%x|which mplayer|.chomp | |
end | |
end | |
def install | |
%x|curl '#{INSTALL_URL}' > #{DOWNLOAD_PATH}| unless File.exists?(DOWNLOAD_PATH) | |
Dir.chdir('/tmp') do | |
%x|unzip #{DOWNLOAD_PATH}| | |
end | |
end | |
end | |
class VideoDownloader | |
DOWNLOAD_PATH = '/tmp' | |
attr_reader :url | |
def self.download(url) | |
downloader = new(url) | |
downloader.download | |
downloader | |
end | |
def initialize(url) | |
@url = URI.parse(url) | |
ensure_url_is_valid | |
end | |
def id | |
@id ||= query_string['v'] | |
end | |
def download | |
system("#{youtube_dl} -o #{download_path} '#{url}'") | |
end | |
def youtube_dl | |
YoutubeDLCommandLineUtility.path | |
end | |
def download_path | |
File.join(DOWNLOAD_PATH, target_file_name) | |
end | |
private | |
def target_file_name | |
"#{id}.flv" | |
end | |
def ensure_url_is_valid | |
if id.nil? | |
raise ArgumentError, "YouTube url '#{url}' doesn't include video identifier" | |
end | |
end | |
def query_string | |
@query_string ||= url.query.split('&').inject({}) do |params, param| | |
key, value = param.split('=') | |
params[key] = value | |
params | |
end | |
end | |
end | |
class SampleGenerator | |
attr_reader :source_file, :start_time, :duration | |
def self.generate(configuration) | |
generator = new(configuration) | |
generator.generate | |
end | |
def initialize(configuration) | |
@source_file = configuration[:source_file] | |
@start_time = configuration[:start_time] | |
@duration = configuration[:duration] | |
@bitrate = configuration[:bitrate] || 256 | |
@bitrate_type = configuration[:bitrate_type] || :constant | |
@output_file = configuration[:output_file] | |
end | |
def generate | |
%x{'#{mplayer}' -nocorrect-pts -ao pcm:file=#{wav_output_path} -vo null -vc null #{source_file} -ss #{start_time} -endpos #{duration}} | |
if !asking_for_wav? && has_lame? | |
AudioConverter.process(wav_output_path, mp3_output_path, { @bitrate_type => @bitrate }) | |
%x{rm #{wav_output_path}} | |
mp3_output_path | |
else | |
wav_output_path | |
end | |
end | |
def mplayer | |
MplayerCommandLineUtility.path | |
end | |
def mp3_output_path | |
@output_file.nil? ? "#{basename}.mp3" : @output_file | |
end | |
def wav_output_path | |
asking_for_wav? ? @output_file : "#{basename}.wav" | |
end | |
private | |
def basename | |
File.basename(source_file, '.*') | |
end | |
def has_lame? | |
(%x|which lame| && $?.success?) | |
end | |
def asking_for_wav? | |
!@output_file.nil? && @output_file.split(/\./).last.to_s.downcase == "wav" | |
end | |
end | |
class AudioConverter | |
def self.process(input, output, bitrate) | |
converter = new(input, output, bitrate) | |
converter.process | |
end | |
def initialize(input, output, bitrate) | |
@bitrate = bitrate | |
@input = input | |
@output = output | |
end | |
def process | |
%x{'lame' "#{@input}" #{args}} | |
end | |
private | |
def args | |
"#{bitrate_as_args} #{output_as_args}" | |
end | |
def output_as_args | |
"-o #{@output}" | |
end | |
def bitrate_as_args | |
key, val = *@bitrate.to_a.first | |
case key | |
when :constant then "-b #{val}" | |
when :variable then "-V #{val}" | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment