require 'uri' require 'open-uri' require 'tempfile' require 'youtube-dl.rb' require 'streamio-ffmpeg' # @!visibility private class QueueItem attr_accessor :next, :prev attr_reader :time, :ptime, :size, :fname, :descr, :is_stream, :is_file def initialize uri, stream = false @is_stream = stream if uri.scheme == "file" @fname = uri.path @is_file = true elsif uri.host and uri.host.end_with? "youtube.com" or uri.host.end_with? "youtu.be" @fname = get_yt_vid_from uri @is_file = true else @fname = uri.to_s @is_file = false end @descr = uri.to_s unless @is_stream if (mov = FFMPEG::Movie.new(@fname)) and mov.valid? @time = mov.duration @size = mov.size @ptime = "%.2i:%.2i" % [@time / 60, @time % 60] else throw ArgumentError, "File is invalid" end end end def file open @fname end def cleanup File.delete @tmpf if @tmpf end private def get_yt_vid_from uri yt_tmp = get_tmp_name "vrobot4_yt_temp_", ".m4a" mv_tmp = get_tmp_name "vrobot4_mv_temp_", ".mp3" opt = { output: yt_tmp, extract_audio: true, audio_format: "m4a" } YoutubeDL.get uri.to_s, opt FFMPEG::Movie.new(yt_tmp).transcode(mv_tmp) File.delete yt_tmp @tmpf = mv_tmp end def get_tmp_name name, ext fname = Dir::Tmpname.tmpdir + "/" fname += Dir::Tmpname.make_tmpname name, ext end end class Mod_Audio < Vrobot4::Module::Module def self.type() "Audio" end Vrobot4::Module.add_module_type self, servflags: "A" def initialize info super register :c_join, "voicejoin", "Brings the bot to your voice channel." register :c_quit, "voicequit", "Kicks the bot from voice.", roles: "h" register :c_queue, "queue", "Adds an item to the queue or lists it." register :c_volume, "volume", "Sets the bot's volume." register :c_skip, "skip", "Skips to the next song." register :c_pause, "pause", "Pauses the current song." register :c_play, "play", "Resumes the current song." register :c_rmqueue, "rmqueue", "Removes an item from the queue." # TODO: add stream playing command @queue = [] @queue_mtx = Mutex.new @running = false end def c_join m, argv vc = m.user.real.voice_channel raise RuntimeError, "You're not in a voice channel" unless vc m.serv.bot.voice_connect vc end def c_quit m, argv m.serv.voice_quit end def c_queue m, argv if argv.empty? list_queue m elsif push_queue m, argv and (@queue.size == 1 or not @running) start_playback m, 0 end end def c_rmqueue m, argv begin @queue_mtx.lock rm_queue @queue[argv.to_i + 1] ensure @queue_mtx.unlock end end def c_volume m, argv num = argv.to_f num = num > 1 ? 1 : num < 0.1 ? 0.1 : num m.serv.bot.voice(m.chan.real).volume = num end def c_skip m, argv m.serv.bot.voice(m.chan.real).stop_playing end def c_pause m, argv m.serv.bot.voice(m.chan.real).pause end def c_play m, argv m.serv.bot.voice(m.chan.real).play end private def list_queue m begin @queue_mtx.lock text = "" i = 1 for qi in @queue text << "%i - <%s> (%s" % [i, qi.descr, qi.ptime] text << ", %i bytes" % [qi.size] unless qi.is_stream text << ")\n" i += 1 end if text.empty? then m.reply "No items in queue." else m.reply_b text end ensure @queue_mtx.unlock end end def push_queue m, argv, stream = false uri = URI.parse argv m.reply "Queueing <" + uri.to_s + ">..." m.chan.real.start_typing if m.serv.class.type == "Discord" begin qi = QueueItem.new(uri, stream) begin @queue_mtx.lock if @queue.any? @queue.last.next = qi qi.prev = @queue.last end @queue.push qi ensure @queue_mtx.unlock end if qi.is_stream m.reply "Stream <" + uri.to_s + "> queued" else m.reply "<" + uri.to_s + "> loaded (%s, %i bytes)" % [qi.ptime, qi.size] end return true rescue p $! m.reply "Couldn't load queue item" return false end end def rm_queue qi @queue.delete qi qi.prev.next = qi.next if qi.prev qi.next.prev = qi.prev if qi.next qi.cleanup end def start_playback m, start @running = true begin @queue_mtx.lock qi = @queue[start] ensure @queue_mtx.unlock end Thread.new do loop do begin if qi.is_file then m.serv.voice_play_io m, qi.file else m.serv.voice_play m, qi.fname end rescue p $! m.reply "Error playing queue item." end begin @queue_mtx.lock rm_queue qi ensure @queue_mtx.unlock end unless (qi = qi.next) m.reply "End of queue reached." @running = false break end end end end end ## EOF