1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
# -*- encoding: binary -*-
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net>
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
require_relative '../dtas'
require_relative 'command'
require_relative 'format'
require_relative 'replaygain'
require_relative 'process'
require_relative 'serialize'
# this is usually one input file
class DTAS::Source # :nodoc:
attr_reader :infile
attr_reader :offset
require_relative 'source/common'
require_relative 'source/mp3'
include DTAS::Command
include DTAS::Process
include DTAS::Source::Common
include DTAS::Source::Mp3
SOURCE_DEFAULTS = COMMAND_DEFAULTS.merge(
"command" => 'exec sox "$INFILE" $SOXFMT - $TRIMFX $RGFX',
"comments" => nil,
)
SIVS = %w(infile comments command env)
def initialize(infile, offset = nil)
command_init(SOURCE_DEFAULTS)
@format = nil
@infile = infile
@offset = offset
@comments = nil
@samples = nil
end
# this exists mainly to make the mpris interface easier, but it's not
# necessary, the mpris interface also knows the sample rate
def offset_us
(offset_samples / format.rate.to_f) * 1000000
end
# returns any offset in samples (relative to the original source file),
# likely zero unless seek was used
def offset_samples
return 0 unless @offset
case @offset
when /\A\d+s\z/
@offset.to_i
else
format.hhmmss_to_samples(@offset)
end
end
def precision
qx(%W(soxi -p #@infile), err: "/dev/null").to_i # sox.git f4562efd0aa3
rescue # fallback to parsing the whole output
s = qx(%W(soxi #@infile), err: "/dev/null")
s =~ /Precision\s+:\s*(\d+)-bit/
v = $1.to_i
return v if v > 0
raise TypeError, "could not determine precision for #@infile"
end
def format
@format ||= begin
fmt = DTAS::Format.new
fmt.from_file(@infile)
fmt.bits ||= precision
fmt
end
end
# A user may be downloading the file and start playing
# it before the download completes, this refreshes
def samples!
@samples = nil
samples
end
# This is the number of samples according to the samples in the source
# file itself, not the decoded output
def samples
@samples ||= qx(%W(soxi -s #@infile)).to_i
rescue => e
warn e.message
0
end
# just run soxi -a
def __load_comments
tmp = {}
case @infile
when String
err = ""
cmd = %W(soxi -a #@infile)
begin
qx(cmd, err: err).split(/\n/).each do |line|
key, value = line.split(/=/, 2)
key && value or next
# TODO: multi-line/multi-value/repeated tags
tmp[key.upcase] = value
end
rescue => e
if /FAIL formats: no handler for file extension/ =~ err
warn("#{xs(cmd)}: #{err}")
else
warn("#{e.message} (#{e.class})")
end
# TODO: fallbacks
end
end
tmp
end
def comments
@comments ||= __load_comments
end
def replaygain
DTAS::ReplayGain.new(comments) || DTAS::ReplayGain.new(mp3gain_comments)
end
def spawn(format, rg_state, opts)
raise "BUG: #{self.inspect}#spawn called twice" if @to_io
e = format.to_env
e["INFILE"] = @infile
# make sure these are visible to the "current" command...
@env["TRIMFX"] = @offset ? "trim #@offset" : nil
@env["RGFX"] = rg_state.effect(self) || nil
@pid = dtas_spawn(e.merge!(@env), command_string, opts)
end
def to_hsh
to_hash.delete_if { |k,v| v == SOURCE_DEFAULTS[k] }
end
def to_hash
rv = ivars_to_hash(SIVS)
rv["samples"] = samples
rv
end
end
|