ruby
about
heres a collection of ruby scripts i’ve created for various tasks. feel free to use/modify them as you need.
yt
downloads from youtube using yt-dlp and renames videos to fit my file naming scheme.
# download any links if given
ARGV&.each do |url|
`yt-dlp --format mp4 "#{url}"`
end
= Dir.pwd
folder_path
# organize files in folder that end in .mp4
Dir.glob("#{folder_path}/*.mp4") do |oldname|
# yt-dlp addes a hash in brackets after video title
= /\[[\w-]*\]/
bracket_hash = Regexp.union bracket_hash, /\W/
filter # ' ' -> '_', remove non-word chars, and downcase file names
= File.basename(oldname).gsub(/ /, '_').gsub(filter, '').gsub(/_?mp4$/, '.mp4').downcase.gsub(/__/, '_')
new_name File.rename oldname, "#{folder_path}/#{new_name}"
end
aria
wrapper around aria2c with pre-set options and a personalized cli interface
require 'optparse'
# build the aria command
def build_command(urls)
puts
= [
cmd 'aria2c',
'--no-conf',
'--conditional-get',
'-x', @options[:num],
'-s', @options[:num],
'-k', "#{@options[:size]}M",
'-j', @options[:parallel]
]
.push '-q' if @options[:quiet]
cmd
if !@options[:file].nil?
.push "-i #{file}"
cmdelse
.each do |url|
urls.push url
cmdend
end
.join ' '
cmdend
@options = { num: 16, size: 1, file: nil, parallel: 4, quiet: false, verbose: false }
OptionParser.new do |opt|
.on('-n', '--num NUMBER') { |o| @options[:num] = o }
opt.on('-s', '--size SIZE') { |o| @options[:size] = o }
opt.on('-f', '--file INPUT_FILE') { |o| @options[:file] = o }
opt.on('-p', '--parallel NUMBER') { |o| @options[:parallel] = o }
opt.on('-q', '--quiet') { |_o| @options[:quiet] = true }
opt.on('-v', '--verbose') { |_o| @options[:verbose] = true }
optend.parse!
= []
urls ARGV.each do |url|
puts url if @options[:verbose]
.push(url) if url.match?('http')
urlsend
= build_command urls
cmd
puts cmd if @options[:verbose]
system build_command(urls)
dir_tag
sorts, renames and tags a directory of music files. if your music folder is setup in a ARTIST/ALBUM/NUMBER_TITLE.[mp3|flac] heirarchy it will automatically figure everything out (according to my preferences). requires ffmpeg
require 'optparse'
def tag(track)
= File.basename(track)
track = /(?<num>[0-9]*)_?(?<title>.*).(?<ext>mp3|flac)/.match(track)
m
= m[:num]
num = '00' if num.empty?
num = path_to_title(m[:title]) unless @options.include?(:title)
title = "tagged.#{num}_#{title.downcase.tr(' ', '_')}.#{m[:ext]}"
path
puts "#{num} #{title} - #{@options[:album]} (#{@options[:artist]})"
system(build_command(num, title, track, path))
# File.unlink(File.expand_path(track))
File.rename(path, path.sub(/^tagged./, ''))
end
def build_command(num, title, track, path)
= ['ffmpeg', '-loglevel', 'error', '-hide_banner', '-y', '-i', "'#{File.expand_path(track)}'"]
cmd @options.each_pair do |key, val|
.push('-metadata')
cmd.push("#{key}='#{val}'")
cmdend
.push("-metadata album_artist='#{@options[:artist]}'")
cmd.push("-metadata track='#{num}'")
cmd.push("-metadata title='#{title}'")
cmd.push("'#{path}'")
cmd.join(' ')
cmdend
def path_to_title(string)
= %w[a of and the]
word_list = string.downcase.split('_')
a = []
b
.each_with_index do |word, index|
a.push(index.zero? || !word_list.include?(word) ? word.capitalize : word)
bend
.join(' ')
bend
@options = {}
OptionParser.new do |opt|
.on('-t', '--title TITLE') { |o| @options[:title] = o }
opt.on('-b', '--album ALBUM') { |o| @options[:album] = o }
opt.on('-a', '--artist ARTIST') { |o| @options[:artist] = o }
opt.on('-n', '--track NUMBER') { |o| @options[:track] = o }
opt.on('-g', '--genre GENRE') { |o| @options[:genre] = o }
optend.parse!
@options[:album] = path_to_title(File.basename(Dir.pwd)) unless @options.include?(:album)
@options[:artist] = path_to_title(File.basename(File.dirname(Dir.pwd))) unless @options.include?(:artist)
= []
tracks
ARGV.each do |track|
.push(track) if /.*\.(mp3|flac)/.match?(track)
tracksend
.each { |t| tag t } tracks
cue_split
splits a .cue track, then converts the resulting .wav to .flac. requires ffmpeg
# song file
class Song
attr_reader :path
def initialize(file_name)
@path = File.absolute_path file_name
@name = File.basename @path
@song_name = File.basename @path, '.*'
@dir = File.dirname @path
end
def to_flac
= "#{@song_name}.flac"
new_name puts "#{@name} -> #{new_name}"
`ffmpeg -loglevel error -hide_banner -nostats -i "#{@path}" "#{@dir}/#{new_name}"`
File.delete @path
regen_path new_nameend
def clean_name
= @name.gsub(/[^\w.-]/, '').gsub(/\._|__|_\./, '_').downcase
new_name return if new_name == @name
File.rename @path, "#{@dir}/#{new_name}"
regen_path new_nameend
def regen_path(new_name)
@path = "#{@dir}/#{new_name}"
end
end
# folder of music files and other folders
class MusicDir < Dir
def initialize(file_name)
super
@path = File.absolute_path file_name
@name = File.basename @path
@parent = File.dirname @path
end
def split_cue
= Dir.glob('*.cue')
cue .each do |song|
cue= song.sub(/.cue$/, '.flac')
flac system('shnsplit', '-f', song, flac)
end
end
def clean_dir
do |child|
each_child = "#{@path}/#{child}"
file if File.directory? file
MusicDir.new(file).clean_dir
else
= Song.new(file)
w .clean_name
w.to_flac unless w.path =~ Regexp.union(/.mp3$/, /.flac$/, /.jpg$/, /.png$/)
wend
end
end
end
= MusicDir.new(Dir.pwd)
d .split_cue
d.clean_dir d