stream2tracks 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Ben Armstrong <synrg@debian.org>
data/COPYING ADDED
@@ -0,0 +1,381 @@
1
+ COPYING for stream2tracks
2
+
3
+ stream2tracks - Download stream as converted, tagged, renamed tracks
4
+
5
+ Copyright (c) 2011 by Ben Armstrong <synrg@debian.org>
6
+
7
+ Distributed under the terms of the GPL License, version 2 or (at your option)
8
+ any later version, a copy of which is included below.
9
+
10
+ stream2tracks also includes the following files under the following license
11
+ terms:
12
+
13
+ setup.rb - a generic installer for Ruby programs
14
+
15
+ It was downloaded from http://i.loveruby.net/en/projects/setup/
16
+
17
+ Upstream Author:
18
+
19
+ Minero Aoki <aamine@loveruby.net>
20
+
21
+ Copyright:
22
+
23
+ Copyright (c) 2000-2005 Minero Aoki
24
+
25
+ License:
26
+
27
+ This package is free software; you can redistribute it and/or
28
+ modify it under the terms of the GNU Lesser General Public
29
+ License as published by the Free Software Foundation, version 2.1 of
30
+ the License.
31
+
32
+ This package is distributed in the hope that it will be useful,
33
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
34
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
35
+ Lesser General Public License for more details.
36
+
37
+ You should have received a copy of the GNU Lesser General Public
38
+ License along with this package; if not, write to the Free Software
39
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
40
+
41
+ -------------------------------------
42
+
43
+ GNU GENERAL PUBLIC LICENSE
44
+ Version 2, June 1991
45
+
46
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
47
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
48
+ Everyone is permitted to copy and distribute verbatim copies
49
+ of this license document, but changing it is not allowed.
50
+
51
+ Preamble
52
+
53
+ The licenses for most software are designed to take away your
54
+ freedom to share and change it. By contrast, the GNU General Public
55
+ License is intended to guarantee your freedom to share and change free
56
+ software--to make sure the software is free for all its users. This
57
+ General Public License applies to most of the Free Software
58
+ Foundation's software and to any other program whose authors commit to
59
+ using it. (Some other Free Software Foundation software is covered by
60
+ the GNU Lesser General Public License instead.) You can apply it to
61
+ your programs, too.
62
+
63
+ When we speak of free software, we are referring to freedom, not
64
+ price. Our General Public Licenses are designed to make sure that you
65
+ have the freedom to distribute copies of free software (and charge for
66
+ this service if you wish), that you receive source code or can get it
67
+ if you want it, that you can change the software or use pieces of it
68
+ in new free programs; and that you know you can do these things.
69
+
70
+ To protect your rights, we need to make restrictions that forbid
71
+ anyone to deny you these rights or to ask you to surrender the rights.
72
+ These restrictions translate to certain responsibilities for you if you
73
+ distribute copies of the software, or if you modify it.
74
+
75
+ For example, if you distribute copies of such a program, whether
76
+ gratis or for a fee, you must give the recipients all the rights that
77
+ you have. You must make sure that they, too, receive or can get the
78
+ source code. And you must show them these terms so they know their
79
+ rights.
80
+
81
+ We protect your rights with two steps: (1) copyright the software, and
82
+ (2) offer you this license which gives you legal permission to copy,
83
+ distribute and/or modify the software.
84
+
85
+ Also, for each author's protection and ours, we want to make certain
86
+ that everyone understands that there is no warranty for this free
87
+ software. If the software is modified by someone else and passed on, we
88
+ want its recipients to know that what they have is not the original, so
89
+ that any problems introduced by others will not reflect on the original
90
+ authors' reputations.
91
+
92
+ Finally, any free program is threatened constantly by software
93
+ patents. We wish to avoid the danger that redistributors of a free
94
+ program will individually obtain patent licenses, in effect making the
95
+ program proprietary. To prevent this, we have made it clear that any
96
+ patent must be licensed for everyone's free use or not licensed at all.
97
+
98
+ The precise terms and conditions for copying, distribution and
99
+ modification follow.
100
+
101
+ GNU GENERAL PUBLIC LICENSE
102
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
103
+
104
+ 0. This License applies to any program or other work which contains
105
+ a notice placed by the copyright holder saying it may be distributed
106
+ under the terms of this General Public License. The "Program", below,
107
+ refers to any such program or work, and a "work based on the Program"
108
+ means either the Program or any derivative work under copyright law:
109
+ that is to say, a work containing the Program or a portion of it,
110
+ either verbatim or with modifications and/or translated into another
111
+ language. (Hereinafter, translation is included without limitation in
112
+ the term "modification".) Each licensee is addressed as "you".
113
+
114
+ Activities other than copying, distribution and modification are not
115
+ covered by this License; they are outside its scope. The act of
116
+ running the Program is not restricted, and the output from the Program
117
+ is covered only if its contents constitute a work based on the
118
+ Program (independent of having been made by running the Program).
119
+ Whether that is true depends on what the Program does.
120
+
121
+ 1. You may copy and distribute verbatim copies of the Program's
122
+ source code as you receive it, in any medium, provided that you
123
+ conspicuously and appropriately publish on each copy an appropriate
124
+ copyright notice and disclaimer of warranty; keep intact all the
125
+ notices that refer to this License and to the absence of any warranty;
126
+ and give any other recipients of the Program a copy of this License
127
+ along with the Program.
128
+
129
+ You may charge a fee for the physical act of transferring a copy, and
130
+ you may at your option offer warranty protection in exchange for a fee.
131
+
132
+ 2. You may modify your copy or copies of the Program or any portion
133
+ of it, thus forming a work based on the Program, and copy and
134
+ distribute such modifications or work under the terms of Section 1
135
+ above, provided that you also meet all of these conditions:
136
+
137
+ a) You must cause the modified files to carry prominent notices
138
+ stating that you changed the files and the date of any change.
139
+
140
+ b) You must cause any work that you distribute or publish, that in
141
+ whole or in part contains or is derived from the Program or any
142
+ part thereof, to be licensed as a whole at no charge to all third
143
+ parties under the terms of this License.
144
+
145
+ c) If the modified program normally reads commands interactively
146
+ when run, you must cause it, when started running for such
147
+ interactive use in the most ordinary way, to print or display an
148
+ announcement including an appropriate copyright notice and a
149
+ notice that there is no warranty (or else, saying that you provide
150
+ a warranty) and that users may redistribute the program under
151
+ these conditions, and telling the user how to view a copy of this
152
+ License. (Exception: if the Program itself is interactive but
153
+ does not normally print such an announcement, your work based on
154
+ the Program is not required to print an announcement.)
155
+
156
+ These requirements apply to the modified work as a whole. If
157
+ identifiable sections of that work are not derived from the Program,
158
+ and can be reasonably considered independent and separate works in
159
+ themselves, then this License, and its terms, do not apply to those
160
+ sections when you distribute them as separate works. But when you
161
+ distribute the same sections as part of a whole which is a work based
162
+ on the Program, the distribution of the whole must be on the terms of
163
+ this License, whose permissions for other licensees extend to the
164
+ entire whole, and thus to each and every part regardless of who wrote it.
165
+
166
+ Thus, it is not the intent of this section to claim rights or contest
167
+ your rights to work written entirely by you; rather, the intent is to
168
+ exercise the right to control the distribution of derivative or
169
+ collective works based on the Program.
170
+
171
+ In addition, mere aggregation of another work not based on the Program
172
+ with the Program (or with a work based on the Program) on a volume of
173
+ a storage or distribution medium does not bring the other work under
174
+ the scope of this License.
175
+
176
+ 3. You may copy and distribute the Program (or a work based on it,
177
+ under Section 2) in object code or executable form under the terms of
178
+ Sections 1 and 2 above provided that you also do one of the following:
179
+
180
+ a) Accompany it with the complete corresponding machine-readable
181
+ source code, which must be distributed under the terms of Sections
182
+ 1 and 2 above on a medium customarily used for software interchange; or,
183
+
184
+ b) Accompany it with a written offer, valid for at least three
185
+ years, to give any third party, for a charge no more than your
186
+ cost of physically performing source distribution, a complete
187
+ machine-readable copy of the corresponding source code, to be
188
+ distributed under the terms of Sections 1 and 2 above on a medium
189
+ customarily used for software interchange; or,
190
+
191
+ c) Accompany it with the information you received as to the offer
192
+ to distribute corresponding source code. (This alternative is
193
+ allowed only for noncommercial distribution and only if you
194
+ received the program in object code or executable form with such
195
+ an offer, in accord with Subsection b above.)
196
+
197
+ The source code for a work means the preferred form of the work for
198
+ making modifications to it. For an executable work, complete source
199
+ code means all the source code for all modules it contains, plus any
200
+ associated interface definition files, plus the scripts used to
201
+ control compilation and installation of the executable. However, as a
202
+ special exception, the source code distributed need not include
203
+ anything that is normally distributed (in either source or binary
204
+ form) with the major components (compiler, kernel, and so on) of the
205
+ operating system on which the executable runs, unless that component
206
+ itself accompanies the executable.
207
+
208
+ If distribution of executable or object code is made by offering
209
+ access to copy from a designated place, then offering equivalent
210
+ access to copy the source code from the same place counts as
211
+ distribution of the source code, even though third parties are not
212
+ compelled to copy the source along with the object code.
213
+
214
+ 4. You may not copy, modify, sublicense, or distribute the Program
215
+ except as expressly provided under this License. Any attempt
216
+ otherwise to copy, modify, sublicense or distribute the Program is
217
+ void, and will automatically terminate your rights under this License.
218
+ However, parties who have received copies, or rights, from you under
219
+ this License will not have their licenses terminated so long as such
220
+ parties remain in full compliance.
221
+
222
+ 5. You are not required to accept this License, since you have not
223
+ signed it. However, nothing else grants you permission to modify or
224
+ distribute the Program or its derivative works. These actions are
225
+ prohibited by law if you do not accept this License. Therefore, by
226
+ modifying or distributing the Program (or any work based on the
227
+ Program), you indicate your acceptance of this License to do so, and
228
+ all its terms and conditions for copying, distributing or modifying
229
+ the Program or works based on it.
230
+
231
+ 6. Each time you redistribute the Program (or any work based on the
232
+ Program), the recipient automatically receives a license from the
233
+ original licensor to copy, distribute or modify the Program subject to
234
+ these terms and conditions. You may not impose any further
235
+ restrictions on the recipients' exercise of the rights granted herein.
236
+ You are not responsible for enforcing compliance by third parties to
237
+ this License.
238
+
239
+ 7. If, as a consequence of a court judgment or allegation of patent
240
+ infringement or for any other reason (not limited to patent issues),
241
+ conditions are imposed on you (whether by court order, agreement or
242
+ otherwise) that contradict the conditions of this License, they do not
243
+ excuse you from the conditions of this License. If you cannot
244
+ distribute so as to satisfy simultaneously your obligations under this
245
+ License and any other pertinent obligations, then as a consequence you
246
+ may not distribute the Program at all. For example, if a patent
247
+ license would not permit royalty-free redistribution of the Program by
248
+ all those who receive copies directly or indirectly through you, then
249
+ the only way you could satisfy both it and this License would be to
250
+ refrain entirely from distribution of the Program.
251
+
252
+ If any portion of this section is held invalid or unenforceable under
253
+ any particular circumstance, the balance of the section is intended to
254
+ apply and the section as a whole is intended to apply in other
255
+ circumstances.
256
+
257
+ It is not the purpose of this section to induce you to infringe any
258
+ patents or other property right claims or to contest validity of any
259
+ such claims; this section has the sole purpose of protecting the
260
+ integrity of the free software distribution system, which is
261
+ implemented by public license practices. Many people have made
262
+ generous contributions to the wide range of software distributed
263
+ through that system in reliance on consistent application of that
264
+ system; it is up to the author/donor to decide if he or she is willing
265
+ to distribute software through any other system and a licensee cannot
266
+ impose that choice.
267
+
268
+ This section is intended to make thoroughly clear what is believed to
269
+ be a consequence of the rest of this License.
270
+
271
+ 8. If the distribution and/or use of the Program is restricted in
272
+ certain countries either by patents or by copyrighted interfaces, the
273
+ original copyright holder who places the Program under this License
274
+ may add an explicit geographical distribution limitation excluding
275
+ those countries, so that distribution is permitted only in or among
276
+ countries not thus excluded. In such case, this License incorporates
277
+ the limitation as if written in the body of this License.
278
+
279
+ 9. The Free Software Foundation may publish revised and/or new versions
280
+ of the General Public License from time to time. Such new versions will
281
+ be similar in spirit to the present version, but may differ in detail to
282
+ address new problems or concerns.
283
+
284
+ Each version is given a distinguishing version number. If the Program
285
+ specifies a version number of this License which applies to it and "any
286
+ later version", you have the option of following the terms and conditions
287
+ either of that version or of any later version published by the Free
288
+ Software Foundation. If the Program does not specify a version number of
289
+ this License, you may choose any version ever published by the Free Software
290
+ Foundation.
291
+
292
+ 10. If you wish to incorporate parts of the Program into other free
293
+ programs whose distribution conditions are different, write to the author
294
+ to ask for permission. For software which is copyrighted by the Free
295
+ Software Foundation, write to the Free Software Foundation; we sometimes
296
+ make exceptions for this. Our decision will be guided by the two goals
297
+ of preserving the free status of all derivatives of our free software and
298
+ of promoting the sharing and reuse of software generally.
299
+
300
+ NO WARRANTY
301
+
302
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
303
+ FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
304
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
305
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
306
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
307
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
308
+ TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
309
+ PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
310
+ REPAIR OR CORRECTION.
311
+
312
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
313
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
314
+ REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
315
+ INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
316
+ OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
317
+ TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
318
+ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
319
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
320
+ POSSIBILITY OF SUCH DAMAGES.
321
+
322
+ END OF TERMS AND CONDITIONS
323
+
324
+ How to Apply These Terms to Your New Programs
325
+
326
+ If you develop a new program, and you want it to be of the greatest
327
+ possible use to the public, the best way to achieve this is to make it
328
+ free software which everyone can redistribute and change under these terms.
329
+
330
+ To do so, attach the following notices to the program. It is safest
331
+ to attach them to the start of each source file to most effectively
332
+ convey the exclusion of warranty; and each file should have at least
333
+ the "copyright" line and a pointer to where the full notice is found.
334
+
335
+ <one line to give the program's name and a brief idea of what it does.>
336
+ Copyright (C) <year> <name of author>
337
+
338
+ This program is free software; you can redistribute it and/or modify
339
+ it under the terms of the GNU General Public License as published by
340
+ the Free Software Foundation; either version 2 of the License, or
341
+ (at your option) any later version.
342
+
343
+ This program is distributed in the hope that it will be useful,
344
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
345
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
346
+ GNU General Public License for more details.
347
+
348
+ You should have received a copy of the GNU General Public License along
349
+ with this program; if not, write to the Free Software Foundation, Inc.,
350
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
351
+
352
+ Also add information on how to contact you by electronic and paper mail.
353
+
354
+ If the program is interactive, make it output a short notice like this
355
+ when it starts in an interactive mode:
356
+
357
+ Gnomovision version 69, Copyright (C) year name of author
358
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
359
+ This is free software, and you are welcome to redistribute it
360
+ under certain conditions; type `show c' for details.
361
+
362
+ The hypothetical commands `show w' and `show c' should show the appropriate
363
+ parts of the General Public License. Of course, the commands you use may
364
+ be called something other than `show w' and `show c'; they could even be
365
+ mouse-clicks or menu items--whatever suits your program.
366
+
367
+ You should also get your employer (if you work as a programmer) or your
368
+ school, if any, to sign a "copyright disclaimer" for the program, if
369
+ necessary. Here is a sample; alter the names:
370
+
371
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
372
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
373
+
374
+ <signature of Ty Coon>, 1 April 1989
375
+ Ty Coon, President of Vice
376
+
377
+ This General Public License does not permit incorporating your program into
378
+ proprietary programs. If your program is a subroutine library, you may
379
+ consider it more useful to permit linking proprietary applications with the
380
+ library. If this is what you want to do, use the GNU Lesser General
381
+ Public License instead of this License.
data/README ADDED
@@ -0,0 +1,84 @@
1
+ stream2tracks-
2
+
3
+ A script to download all tracks from a stream container, transcode them all
4
+ to the desired format, tag them using metadata from the streams and rename
5
+ them to filenames derived from the tags.
6
+
7
+ Currently only asx streams are supported as input and formats that are
8
+ supported by ffmpeg and taglib2 (e.g. ogg, flac, mp3) are supported as output.
9
+
10
+ This code is considered early alpha and therefore is not guaranteed to do
11
+ anything useful at all. But if the rough edges don't bother you, it's sort
12
+ of usable already.
13
+
14
+ Download:
15
+
16
+ 1. On Debian Wheezy/Sid, install these dependencies. For other platforms,
17
+ install the equivalents.
18
+
19
+ $ sudo apt-get install mimms ffmpeg
20
+
21
+ 2. Install the gem.
22
+
23
+ $ sudo gem install stream2tracks
24
+
25
+ Usage:
26
+
27
+ $ mkdir -p <output-dir>
28
+ $ cd <output-dir>
29
+ $ stream2tracks <asx-file-or-url>
30
+
31
+ After some time, the script will finish and all output will appear in the
32
+ current directory. Look in $HOME/.cache/stream2tracks/* if anything went
33
+ wrong, as currently the working files are not cleaned up.
34
+
35
+ The default output format is ogg, and I've tested ogg, mp3 and flac, though
36
+ any format supported by both ffmpeg and taglib2 should work. So, for
37
+ example, to output as mp3:
38
+
39
+ $ stream2tracks -f mp3 <asx-file-or-url>
40
+
41
+ There is an experimental 'multi' mode which downloads all tracks at once.
42
+ This may work if you have a broadband connection, but could cause problems
43
+ if you're on a slower link. e.g.
44
+
45
+ $ stream2tracks -m <asx-file-or-url>
46
+
47
+ Where to find streams:
48
+
49
+ A number of radio sites archive concerts and other programs as asx streams.
50
+ I've tested the script with http://www.cbc.ca/radio2/cod so far. There may
51
+ be some variations in the formats and metadata wrapped in the streams, so
52
+ I would be happy to hear about your experience using this script with
53
+ other sites as well.
54
+
55
+ You can also install from source:
56
+
57
+ 1. Fetch the source.
58
+
59
+ $ git clone git://github.com/synrg/stream2tracks.git
60
+
61
+ 2a. Build and install the gem.
62
+
63
+ $ cd stream2tracks
64
+ $ rake package
65
+ $ sudo gem install pkg/stream2tracks*.gem
66
+
67
+ 2b. Or install using Minero Aoki's setup.rb, e.g.
68
+
69
+ $ cd stream2tracks
70
+ $ ruby setup.rb config --prefix=/usr/local
71
+ $ sudo ruby setup.rb install
72
+
73
+ See setup.rb --help for other possible options.
74
+
75
+ Bugs:
76
+
77
+ Please file and track issues at the project site:
78
+
79
+ https://github.com/synrg/stream2tracks
80
+
81
+ The preferred way to contact me about bugs, including documentation bugs
82
+ or wishlists is through the issues system.
83
+
84
+ Ben Armstrong <synrg@debian.org>
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ - support input formats other than asx (must support titles for tagging)
2
+ - provide a more proper project structure: lib, bin, test/rspec/cucumber, Rakefile
3
+ - put code under test
4
+ - packaging (setup, gem, debian)
5
+ - write a man page
6
+ - serialize intermediate results and save to make it possible to
7
+ reprocess stream, picking up where we left off if the rip was
8
+ aborted
@@ -0,0 +1,12 @@
1
+ #! /usr/bin/ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__ + '.rb') + '/../lib')
4
+
5
+ begin
6
+ require 'sfl' # For ruby 1.8 compatibility
7
+ rescue LoadError
8
+ end
9
+
10
+ require 'stream2tracks/cli'
11
+
12
+ stream2tracks ARGV
@@ -0,0 +1,2 @@
1
+ require 'stream2tracks/stream_track_ripper'
2
+ require 'stream2tracks/cli'
@@ -0,0 +1,35 @@
1
+ require 'nokogiri'
2
+ require 'stream2tracks/stream'
3
+
4
+ class ASXStream < Stream
5
+ def entries
6
+ xml.css('ENTRY')
7
+ end
8
+
9
+ def parse
10
+ title=xml.css('ASX>TITLE')[0]
11
+ album=title.inner_text if title
12
+ @tags[:album]=album
13
+ count=0
14
+ tracks=[]
15
+ entries.each do |_|
16
+ tags=@tags.dup
17
+ count+=1
18
+ tags.merge!(
19
+ :title=>_.css('TITLE').inner_text,
20
+ :artist=>_.css('AUTHOR').inner_text,
21
+ :track=>count
22
+ )
23
+ track=Track.new tags,_.css('REF')[0]['HREF']
24
+ tracks << track
25
+ end
26
+ tracks
27
+ end
28
+
29
+ private
30
+
31
+ def xml
32
+ @xml||=Nokogiri::XML(@raw)
33
+ end
34
+ end
35
+
@@ -0,0 +1,47 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ require 'stream2tracks/stream_track_ripper'
4
+
5
+ def stream2tracks argv
6
+ options=OpenStruct.new
7
+ options.format='ogg'
8
+ options.multi=false
9
+ options.debug=false
10
+ options.log=nil
11
+
12
+ OptionParser.new do |opts|
13
+ opts.on('-d','--debug','Output debugging info.') do
14
+ options.debug=true
15
+ end
16
+
17
+ opts.on('-f','--format FORMAT','Convert tracks to FORMAT (default %s).' % options.format) do |format|
18
+ options.format=format
19
+ end
20
+
21
+ opts.on('-l','--log FILENAME','Log to FILENAME (or - for standard error).') do |filename|
22
+ options.log=filename
23
+ end
24
+
25
+ opts.on('-m','--multi','Download multiple tracks at once (EXPERIMENTAL).') do
26
+ options.multi=true
27
+ end
28
+
29
+ opts.on('-q','--quiet','Suppress output (progress bar).') do
30
+ options.quiet=true
31
+ end
32
+
33
+ opts.on_tail('-h', '--help', 'Show this message.') do
34
+ puts opts
35
+ exit
36
+ end
37
+
38
+ opts.on_tail('--version', 'Show version.') do
39
+ puts StreamTrackRipper::Version.join('.')
40
+ exit
41
+ end
42
+ end.parse! argv
43
+
44
+ input_filename=argv.shift
45
+
46
+ StreamTrackRipper.new(ASXStream.new(input_filename),options){}
47
+ end
@@ -0,0 +1,80 @@
1
+ require 'progressbar'
2
+
3
+ # A WatchedProcessGroup is a group of processes running in parallel,
4
+ # all of which can collectively be watched to indicate progress so far.
5
+ # It also supports visually representing progress of all processes
6
+ # in the group as a progress bar.
7
+ class WatchedProcessGroup < Array
8
+ def watch options
9
+ @bar=nil
10
+ update_bar=proc do |current,total|
11
+ # FIXME: remove hardwired 'Downloading' label
12
+ @bar=ProgressBar.new('Downloading',total) if total!=@old_total or @bar.nil?
13
+ @old_total=total
14
+ @bar.set current
15
+ end
16
+
17
+ all_done=false
18
+
19
+ until all_done do
20
+ all_done=true
21
+ current,@old_total,total=0,0,0
22
+ each do |_|
23
+ begin
24
+ status=_.watch options
25
+ rescue RuntimeError => e
26
+ # FIXME: @log belongs to StreamTrackRipper instance, not WatchedProcessGroup!
27
+ # TODO: wrap in an error handler to both log and print to stderr
28
+ @log.error e.message
29
+ $stderr.puts "\nE: %s: %s" % [@cmd,e.message]
30
+ delete _
31
+ end
32
+ if status.current
33
+ # The reported totals may not be accurate. Rather
34
+ # than recompute the actual total when the track
35
+ # finishes, just accumulate the reported total
36
+ # to improve accuracy of the progress bar.
37
+ current+=status.eof ? status.total : status.current
38
+ total+=status.total
39
+ end
40
+ all_done=false unless status.eof
41
+ end
42
+ update_bar[current,total] if total>0
43
+ end
44
+
45
+ @bar.finish if @bar
46
+ end
47
+ end
48
+
49
+ # A WatchedProcess is a process producing output which can be watched
50
+ # to indicate progress so far and validate output.
51
+ # - It supports these behaviours with the following callbacks:
52
+ # progress[@buffer] => [current,total]
53
+ # validate[@buffer] => true when download is complete and successful
54
+ # - validate may raise if an unrecoverable error occurs before the
55
+ # process is complete
56
+ class WatchedProcess
57
+ MAX_READ_LEN=1024 # completely arbitrary
58
+ class Status < Struct.new :eof,:valid,:current,:total ; end
59
+
60
+ def initialize cmd
61
+ @out,out_write=IO.pipe
62
+ @cmd=cmd
63
+ @pid=spawn @cmd,:in=>:close,:out=>out_write,:err=>[:child,:out]
64
+ out_write.close
65
+ yield self if block_given?
66
+ end
67
+
68
+ def watch options
69
+ progress,validate=options.progress,options.validate
70
+ @buffer||=''
71
+ output=@out.readpartial MAX_READ_LEN rescue EOFError
72
+ @buffer << output if output
73
+ $stderr.print output if options.debug and output
74
+ current,total=progress[@buffer] if progress
75
+ eof=@out.eof?
76
+ valid=validate[@buffer] if validate
77
+ Status.new eof,valid,current,total
78
+ end
79
+ end
80
+
@@ -0,0 +1,49 @@
1
+ require 'digest/md5'
2
+ require 'open-uri'
3
+
4
+ # A Stream is an abstraction of the input audio stream container that has:
5
+ # - a path, either a URI or filename
6
+ # - a key, uniquely identifying the stream
7
+ # - some metadata about the stream (Tags)
8
+ # - some entries
9
+ #
10
+ # Tags are metadata about a stream, its entries or a Track
11
+ #
12
+ # A Track is separate entry from a stream, containing:
13
+ # - some metadata about the track (Tags)
14
+ # - a URI
15
+ #
16
+ # A TrackFile is a file produced from a stream, containing:
17
+ # - some metadata about the track (Tags)
18
+ # - the filename
19
+ # - the format of the file
20
+
21
+ class Tags < Hash ; end
22
+
23
+ # TODO: perhaps delegate to tags via method_missing?
24
+ class Track < Struct.new :tags,:uri ; end
25
+
26
+ class TrackFile < Struct.new :tags,:filename,:format ; end
27
+
28
+ # An abstract class
29
+ class Stream
30
+ attr_accessor :tags,:key
31
+
32
+ def initialize path
33
+ @path=path
34
+ file=open(@path)
35
+ @raw=file.read
36
+ @tags=Tags.new
37
+ end
38
+
39
+ # To be implemented in the concrete subclass
40
+ def entries ; end
41
+
42
+ def key
43
+ @key||=Digest::MD5.new.hexdigest(@raw).to_s
44
+ end
45
+
46
+ # To be implemented in the concrete subclass
47
+ def parse ; end
48
+ end
49
+
@@ -0,0 +1,132 @@
1
+ require 'logger'
2
+ require 'taglib'
3
+ require 'fileutils'
4
+ include FileUtils
5
+ require 'stream2tracks/asx_stream'
6
+ require 'stream2tracks/process'
7
+
8
+ class StreamTrackRipper
9
+ Version=['0','0','2']
10
+
11
+ def initialize stream,options
12
+ @stream=stream
13
+ @options=options
14
+ @log=Logger.new @options.log=='-' ? $stderr : @options.log if @options.log
15
+ @tracks=@stream.parse
16
+ @log.info 'Processing stream: %s' % @stream.key if @log
17
+ @log.info 'Found %i tracks' % @tracks.count if @log
18
+ album=@stream.tags[:album]
19
+ @log.info 'Found stream title: %s' % album if @log and album
20
+ @curdir=Dir.pwd
21
+ @workdir=File.join(ENV['HOME'],'.cache','stream2tracks',@stream.key)
22
+ mkdir_p @workdir
23
+ if block_given?
24
+ # TODO: support (re)processing selected tracks from any stage
25
+ trackfiles=get @tracks
26
+ trackfiles=convert trackfiles,@options.format
27
+ trackfiles=tag trackfiles
28
+ @trackfiles=rename trackfiles
29
+ yield
30
+ end
31
+ # TODO: report on any failures
32
+ end
33
+
34
+ TO_BYTES={'GB'=>1000000000,'MB'=>1000000,'KB'=>1000}
35
+ def get tracks
36
+ trackfiles=[]
37
+ processes=WatchedProcessGroup.new
38
+ options=@options.dup
39
+ unless options.quiet
40
+ options.progress=proc do |buffer|
41
+ current,total=if buffer.scan(/(\d+\.\d+) (.B) \/ (\d+\.\d+) (.B)/m).last
42
+ cur,cur_unit,tot,tot_unit=$1,$2,$3,$4
43
+ [(cur.to_f*TO_BYTES[cur_unit.upcase]).to_i,
44
+ (tot.to_f*TO_BYTES[tot_unit.upcase]).to_i]
45
+ end
46
+ [current,total]
47
+ end
48
+ options.validate=proc do |buffer|
49
+ raise 'libmms connection error' if buffer.include? 'libmms error: libmms connection error'
50
+ buffer.include? 'Download complete!'
51
+ end
52
+ end
53
+ tracks.each do |track|
54
+ tags=track.tags
55
+ # TODO: support formats which may not be possible to determine
56
+ # from the file extension of the entry in the stream (using
57
+ # magic number from file itself).
58
+ format=File.extname(File.basename(track.uri)).sub(/^\./,'')
59
+ @log.info 'Track tags: %s' % tags.inspect if @log
60
+ output_filename=File.join @workdir,'%02d.%s' % [tags[:track],format]
61
+ if File.exists? output_filename
62
+ @log.info 'Discarding old partial output: %s' % output_filename if @options.log
63
+ rm output_filename
64
+ end
65
+ cmd='mimms "%s" "%s"' % [track.uri,output_filename]
66
+ @log.info 'Spawning: %s' % cmd if @log
67
+ processes << WatchedProcess.new(cmd)
68
+ trackfiles << TrackFile.new(tags,output_filename,format)
69
+ processes[-1,1].watch options unless @options.multi
70
+ end
71
+ processes.watch options if @options.multi
72
+ trackfiles
73
+ end
74
+
75
+ def convert trackfiles,format
76
+ trackfiles.each do |trackfile|
77
+ tags=trackfile.tags
78
+ input_filename=trackfile.filename
79
+ output_filename=File.join @workdir,'%02d.%s' % [tags[:track],format]
80
+ cmd=case format
81
+ when 'ogg'
82
+ 'ffmpeg -i "%s" -acodec libvorbis "%s"' %
83
+ [input_filename,output_filename]
84
+ else # e.g. mp3, flac
85
+ 'ffmpeg -i "%s" "%s"' %
86
+ [input_filename,output_filename]
87
+ end
88
+ @log.info 'Spawning: %s' % cmd if @log
89
+ WatchedProcessGroup.new([WatchedProcess.new(cmd)]).watch(@options)
90
+ rm input_filename
91
+ trackfile.filename=output_filename
92
+ trackfile.format=format
93
+ end
94
+ trackfiles
95
+ end
96
+
97
+ def tag trackfiles
98
+ trackfiles.each do |trackfile|
99
+ @log.info 'Tagging: %s' % trackfile.filename if @log
100
+ tags=trackfile.tags
101
+ file=TagLib::File.new trackfile.filename
102
+ file.album = tags[:album] if tags[:album]
103
+ file.title = tags[:title]
104
+ file.artist = tags[:artist]
105
+ file.track = tags[:track]
106
+ file.save
107
+ end
108
+ trackfiles
109
+ end
110
+
111
+ def rename trackfiles
112
+ trackfiles.each do |trackfile|
113
+ tags=trackfile.tags
114
+ input_filename=trackfile.filename
115
+ output_filename='%02d-%s-%s.%s' %
116
+ [tags[:track],tags[:album] ?
117
+ tags[:album] :
118
+ tags[:artist],tags[:title],
119
+ trackfile.format]
120
+ # Strip characters that could cause problems for some target filesystems
121
+ output_filename.gsub!(/[:\/\\]/,' ')
122
+ # And reduce any blanks to underscores as a shell typing aid
123
+ output_filename.gsub!(/ +/,'_')
124
+
125
+ @log.info 'Renaming: %s to: %s' % [input_filename,output_filename] if @log
126
+ mv input_filename,output_filename
127
+ trackfile.filename=output_filename
128
+ end
129
+ trackfiles
130
+ end
131
+ end
132
+
@@ -0,0 +1,6 @@
1
+ describe 'Stream' do
2
+ describe '.new' do
3
+ end
4
+ describe '#key' do
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stream2tracks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ben Armstrong
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: &10413680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.5'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *10413680
25
+ - !ruby/object:Gem::Dependency
26
+ name: progressbar
27
+ requirement: &10413220 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '0.9'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *10413220
36
+ - !ruby/object:Gem::Dependency
37
+ name: sfl
38
+ requirement: &10429140 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '2.0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *10429140
47
+ - !ruby/object:Gem::Dependency
48
+ name: taglib2
49
+ requirement: &10428660 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.3
55
+ - - <
56
+ - !ruby/object:Gem::Version
57
+ version: '0.2'
58
+ type: :runtime
59
+ prerelease: false
60
+ version_requirements: *10428660
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: &10428020 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: *10428020
72
+ description: Stream ripper and tagger supporting asx, ogg, mp3, flac, etc.
73
+ email: synrg@debian.org
74
+ executables:
75
+ - stream2tracks
76
+ extensions: []
77
+ extra_rdoc_files:
78
+ - README
79
+ files:
80
+ - AUTHORS
81
+ - README
82
+ - TODO
83
+ - COPYING
84
+ - bin/stream2tracks
85
+ - spec/stream2tracks/stream_spec.rb
86
+ - lib/stream2tracks/process.rb
87
+ - lib/stream2tracks/asx_stream.rb
88
+ - lib/stream2tracks/cli.rb
89
+ - lib/stream2tracks/stream.rb
90
+ - lib/stream2tracks/stream_track_ripper.rb
91
+ - lib/stream2tracks.rb
92
+ homepage: http://github.com/synrg/stream2tracks
93
+ licenses: []
94
+ post_install_message:
95
+ rdoc_options:
96
+ - --main
97
+ - README
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 1.8.11
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: Download stream as converted, tagged, renamed tracks; asx input and ogg,
118
+ mp3, flac, etc. output supported.
119
+ test_files: []