#!/usr/bin/env ruby # Copyright (C) 2013-2016 all contributors # License: GPL-3.0+ # frozen_string_literal: true # encoding: binary # WARNING: totally unstable API, use dtas-ctl for scripting (but the protocol # itself is also unstable, but better than this one probably). require 'dtas/unix_client' require 'shellwords' def get_track_ids(c) track_ids = c.req("tl tracks") # we could get more, but SEQPACKET limits size... track_ids = track_ids.split(' ') track_ids.shift track_ids end def fix_enc!(str, enc) str.force_encoding(enc) str.force_encoding(Encoding::ASCII_8BIT) unless str.valid_encoding? end def do_edit(c) require 'dtas/edit_client' require 'yaml' require 'tempfile' extend DTAS::EditClient tmp = Tempfile.new(%w(dtas-tl-edit .txt)) tmp.binmode tmp_path = tmp.path orig = [] orig_idx = {} enc = Encoding.default_external get_track_ids(c).each_slice(128) do |track_ids| res = c.req("tl get #{track_ids.join(' ')}") res = Shellwords.split(res.sub!(/\A\d+ /, '')) while line = res.shift line.sub!(/\A(\d+)=/n, '') or abort "unexpected line=#{line.inspect}\n" fix_enc!(line, enc) track_id = $1.to_i orig_idx[track_id] = orig.size orig << track_id tmp.write("#{Shellwords.escape(line)} =#{track_id}\n") end end tmp.flush ed = editor # jump to the line of the currently playing track if using vi or vim # Patches for other editors welcome: dtas-all@nongnu.org if ed =~ /vim?\z/ cur = YAML.load(c.req('current')) if tl = cur['tracklist'] if pos = tl['pos'] ed += " +#{pos + 1}" end end end # Run the editor and let the user edit! system("#{ed} #{Shellwords.escape tmp_path}") or return edit = [] edit_idx = {} add = [] # editor may rename/link a new file into place File.open(tmp_path) do |fp| fp.binmode while line = fp.gets line.chomp! if line.sub!(/ =(\d+)\z/n, '') # existing tracks track_id = $1.to_i if edit_idx[track_id] # somebody copy+pasted an existing line add << [ line, edit.last ] else # moved line edit_idx[track_id] = edit.size edit << track_id end else # entirely new line add << [ line, edit.last ] end end end edit.each_with_index do |track_id, i| oi = orig_idx[track_id] or warn("unknown track_id=#{track_id}") or next next if oi == i # no change, yay! prev_track_id = orig[i] or warn("unknown index at #{i}") or next c.req("tl swap #{track_id} #{prev_track_id}") orig_idx[track_id] = i orig_idx[prev_track_id] = oi orig[i] = track_id orig[oi] = prev_track_id end orig.each do |track_id| edit_idx[track_id] or c.req("tl remove #{track_id}") end prev_added_id = last_after_id = nil non_existent = [] add.each do |path, after_id| orig = path path = Shellwords.split(path)[0] path = File.expand_path(path) unless File.exist?(path) path = orig.dup fix_enc!(path, enc) path = Shellwords.split(path)[0] path = File.expand_path(path) end if File.exist?(path) cmd = %W(tl add #{path}) id = after_id == last_after_id ? prev_added_id : after_id cmd << id.to_s if id prev_added_id = c.req(cmd).to_i last_after_id = after_id else non_existent << orig end end if non_existent[0] $stderr.puts "Failed to add #{non_existent.size} paths" non_existent.each { |path| $stderr.puts path } end ensure tmp.close! if tmp end def add_after(c, argv, last_id) argv.each do |path| path = File.expand_path(path) req = %W(tl add #{path}) req << last_id.to_s if last_id res = c.req(req) print "#{path} #{res}\n" last_id = res if res =~ /\A\d+\z/ end end c = DTAS::UNIXClient.new case cmd = ARGV[0] when "cat" enc = Encoding.default_external get_track_ids(c).each_slice(128) do |track_ids| res = c.req("tl get #{track_ids.join(' ')}") res = Shellwords.split(res.sub!(/\A\d+ /, '')) while line = res.shift fix_enc!(line, enc) print "#{line}\n" end end when 'aac' # add-after-current ARGV.shift rv = c.req(%w(tl current-id)) last_id = rv =~ %r{\A\d+\z} ? rv.to_i : nil add_after(c, ARGV, last_id) when "addhead" ARGV.shift ARGV.reverse_each do |path| path = File.expand_path(path) res = c.req(%W(tl add #{path})) print "#{path} #{res}\n" end when "addtail" ARGV.shift track_ids = get_track_ids(c) last_id = track_ids.pop add_after(c, ARGV, last_id) when "reto" fixed = ARGV.delete("-F") ignorecase = ARGV.delete("-i") re = ARGV[1] time = ARGV[2] re = Regexp.quote(re) if fixed re = ignorecase ? %r{#{re}}i : %r{#{re}} get_track_ids(c).each do |track_id| res = c.req("tl get #{track_id}") res.sub!(/\A1 \d+=/, '') if re =~ res req = %W(tl goto #{track_id}) req << time if time res = c.req(req) puts res exit(res == "OK") end end warn "#{re.inspect} not found" exit 1 when 'edit' then do_edit(c) else # act like dtas-ctl for now... puts c.req([ "tl", *ARGV ]) end