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

folder_path = Dir.pwd

# 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
  bracket_hash = /\[[\w-]*\]/
  filter = Regexp.union bracket_hash, /\W/
  # ' ' -> '_', remove non-word chars, and downcase file names
  new_name = File.basename(oldname).gsub(/ /, '_').gsub(filter, '').gsub(/_?mp4$/, '.mp4').downcase.gsub(/__/, '_')
  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[],
    '-s', @options[],
    '-k', "#{@options[]}M",
    '-j', @options[]
  ]

  cmd.push '-q' if @options[]

  if !@options[].nil?
    cmd.push "-i #{file}"
  else
    urls.each do |url|
      cmd.push url
    end
  end

  cmd.join ' '
end

@options = { 16, 1, nil, 4, false, false }
OptionParser.new do |opt|
  opt.on('-n', '--num NUMBER') { |o| @options[] = o }
  opt.on('-s', '--size SIZE') { |o| @options[] = o }
  opt.on('-f', '--file INPUT_FILE') { |o| @options[] = o }
  opt.on('-p', '--parallel NUMBER') { |o| @options[] = o }
  opt.on('-q', '--quiet') { |_o| @options[] = true }
  opt.on('-v', '--verbose') { |_o| @options[] = true }
end.parse!

urls = []
ARGV.each do |url|
  puts url if @options[]
  urls.push(url) if url.match?('http')
end

cmd = build_command urls

puts cmd if @options[]

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)
  track = File.basename(track)
  m = /(?<num>[0-9]*)_?(?<title>.*).(?<ext>mp3|flac)/.match(track)

  num = m[]
  num = '00' if num.empty?
  title = path_to_title(m[]) unless @options.include?()
  path = "tagged.#{num}_#{title.downcase.tr(' ', '_')}.#{m[]}"

  puts "#{num} #{title} - #{@options[]} (#{@options[]})"
  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)
  cmd = ['ffmpeg', '-loglevel', 'error', '-hide_banner', '-y', '-i', "'#{File.expand_path(track)}'"]
  @options.each_pair do |key, val|
    cmd.push('-metadata')
    cmd.push("#{key}='#{val}'")
  end

  cmd.push("-metadata album_artist='#{@options[]}'")
  cmd.push("-metadata track='#{num}'")
  cmd.push("-metadata title='#{title}'")
  cmd.push("'#{path}'")
  cmd.join(' ')
end

def path_to_title(string)
  word_list = %w[a of and the]
  a = string.downcase.split('_')
  b = []

  a.each_with_index do |word, index|
    b.push(index.zero? || !word_list.include?(word) ? word.capitalize : word)
  end
  b.join(' ')
end

@options = {}
OptionParser.new do |opt|
  opt.on('-t', '--title TITLE') { |o| @options[] = o }
  opt.on('-b', '--album ALBUM') { |o| @options[] = o }
  opt.on('-a', '--artist ARTIST') { |o| @options[] = o }
  opt.on('-n', '--track NUMBER') { |o| @options[] = o }
  opt.on('-g', '--genre GENRE') { |o| @options[] = o }
end.parse!

@options[] = path_to_title(File.basename(Dir.pwd)) unless @options.include?()
@options[] = path_to_title(File.basename(File.dirname(Dir.pwd))) unless @options.include?()

tracks = []

ARGV.each do |track|
  tracks.push(track) if /.*\.(mp3|flac)/.match?(track)
end

tracks.each { |t| tag t }

cue_split

splits a .cue track, then converts the resulting .wav to .flac. requires ffmpeg

# song file
class Song
  attr_reader 

  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
    new_name = "#{@song_name}.flac"
    puts "#{@name} -> #{new_name}"
    `ffmpeg -loglevel error -hide_banner -nostats -i "#{@path}" "#{@dir}/#{new_name}"`
    File.delete @path
    regen_path new_name
  end

  def clean_name
    new_name = @name.gsub(/[^\w.-]/, '').gsub(/\._|__|_\./, '_').downcase
    return if new_name == @name

    File.rename @path, "#{@dir}/#{new_name}"
    regen_path new_name
  end

  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
    cue = Dir.glob('*.cue')
    cue.each do |song|
      flac = song.sub(/.cue$/, '.flac')
      system('shnsplit', '-f', song, flac)
    end
  end

  def clean_dir
    each_child do |child|
      file = "#{@path}/#{child}"
      if File.directory? file
        MusicDir.new(file).clean_dir
      else
        w = Song.new(file)
        w.clean_name
        w.to_flac unless w.path =~ Regexp.union(/.mp3$/, /.flac$/, /.jpg$/, /.png$/)
      end
    end
  end
end

d = MusicDir.new(Dir.pwd)
d.split_cue
d.clean_dir