#!/usr/bin/env ruby require 'fileutils' gem 'ffmprb' require 'ffmprb' YOU_VIDEO_OPT = {resolution: '1920x1080', fps: 60} int_video_opt = {resolution: '3840x2160', fps: 60} GOP_MP4_RE = /\b(GX(\d\d)(\d\d\d\d)\.MP4)\b/i GOP_ZIP_URL_RE = %r[/zip/]i def dura_to_sec(dura_str) dura_str.split(':').reverse.each_with_index.reduce(0) do |sec, (ns, i)| sec + ns.to_i*(60**i) end end out_dir = Dir.pwd av_src_cuts = [] Dir.mktmpdir do |tmp_dir| Dir.chdir tmp_dir do warn "Working in #{tmp_dir} --" system "df -h #{tmp_dir}" warn "\nEnter lines containing GoP media D/L URLs and cut times:\n\n" dl_q = Queue.new fetcher = Thread.new do while (url, name, cuts = dl_q.deq) srcs = [] while srcs.empty? # NOTE sometimes (zip) D/L silently fails, see below unless system "curl -so #{name} #{url}" warn "ERROR downloading #{url}" exit 3 end if name == 'tmp.zip' zip_lines = `unzip #{name}`.lines if $?.success? # NOTE if the D/L in fact has failed, it'll be retried zip_lines.each do |line| srcs << $1 if line =~ GOP_MP4_RE end end File.delete name else srcs << name end end av_src_cuts << [ srcs.sort do |a, b| a_m = GOP_MP4_RE.match(a) b_m = GOP_MP4_RE.match(b) if (fst = a_m[3] <=> b_m[3]) != 0 fst else a_m[2] <=> b_m[2] end end, cuts ] end end while (url_cut = gets) url, *cut = url_cut.chomp.split(' ') last_cut = -1 cuts = cut.map do |ns| dura_to_sec(ns).tap do |curr_cut| unless curr_cut > last_cut warn "ERROR cut times must be ascending (look it up)" exit 1 end end end case url when GOP_ZIP_URL_RE dl_q.enq [url, 'tmp.zip', cuts] when GOP_MP4_RE dl_q.enq [url, $1, cuts] else warn "ERROR invalid URL, cannot go on" exit 1 end end dl_q.enq nil warn "\nFetching those files..." fetcher.join warn av_src_cuts.inspect if av_src_cuts.empty? warn "ERROR no inputs given" exit 1 end out_name = av_src_cuts.reduce([]) do |n, sc| n + sc[0].map{ |s| File.basename(s, '.*') } end.join('-') out_path = File.join(out_dir, "#{out_name}-you.mp4") warn "\nCut-catting to #{out_path}..." pipe_cut_threads = av_src_cuts.map do |srcs, cuts| [ (av_pipe = Ffmprb::File.temp_fifo('.flv')), cuts.each_slice(2).map { |from, to| {from: from, to: to} }, Thread.new do Ffmprb.process do output av_pipe, video: int_video_opt do srcs.each do |src| roll input src end end end end ] end Ffmprb.process do output out_path, video: YOU_VIDEO_OPT do pipe_cut_threads.each do |av_pipe, cut_opts, _| (cut_opts.empty?? [{}] : cut_opts).each do |cut_opt| roll input(av_pipe).cut cut_opt end end end end pipe_cut_threads.each do |av_pipe, _, thr| thr.join File.delete av_pipe end end end warn "\nAll done and clean..."