Exercises from Chapter 11

Safer Picture Downloading

Well, since I was asking you to adapt it to your computer, I can’t really show you how to do it. I will show you the program I actually wrote, though.

It’s a bit more complex than the other examples here, partly because it’s a real, working tool.

# For Katy, with love.
### Download pictures from camera card.
require ​'win32ole'
STDOUT.sync = true
Thread.abort_on_exception = true
Dir.chdir ​'C:Documents and SettingsChrisDesktoppictureinbox'
# Always look here for pics.
pic_names = Dir[​'!undated/**/*.{jpg,avi}'​]
thm_names = Dir[​'!undated/**/*.{thm}'​ ]
# Scan for memory cards in the card reader.
WIN32OLE.new(​"Scripting.FileSystemObject"​).Drives.each() ​do​ |x|
#driveType 1 is removable disk
if​ x.DriveType == 1 && x.IsReady
pic_names += Dir[x.DriveLetter+​':/**/*.{jpg,avi}'​]
thm_names += Dir[x.DriveLetter+​':/**/*.{thm}'​ ]
end
end
months = ​%w(jan feb mar apr may jun jul aug sep oct nov dec)
encountered_error = false
print ​"Downloading ​#{pic_names.size}​ files: "
pic_names.each ​do​ |name|
print ​'.'
is_movie = (name[-3..-1].downcase == ​'avi'​)
if​ is_movie
orientation = 0
new_name = File.open(name) ​do​ |f|
f.seek(0x144,IO::SEEK_SET)
f.read(20)
end
new_name[0...3] = ​'%.2d'​ % (1 + months.index(new_name[0...3].downcase))
new_name = new_name[-4..-1] + ​' '​ + new_name[0...-5]
else
new_name, orientation = File.open(name) ​do​ |f|
f.seek(0x36, IO::SEEK_SET)
orientation_ = f.read(1)[0]
f.seek(0xbc, IO::SEEK_SET)
new_name_ = f.read(19)
[new_name_, orientation_]
end
end
[4,7,10,13,16].each {|n| new_name[n] = ​'.'​}
if​ new_name[0] != ​'2'​[0]
encountered_error = true
puts ​"​ ​"​+​'ERROR: Could not process "'​+name+
'" because it​'​s not in the proper format!'
next
end
save_name = new_name + (is_movie ? ​'.orig.avi'​ : ​'.jpg'​)
# Make sure we don't save over another file!!
while​ FileTest.exist? save_name
new_name += ​'a'
save_name = new_name + (is_movie ? ​'.orig.avi'​ : ​'.jpg'​)
end
case​ orientation
when​ 6
`convert "#{name}" -rotate "90>" "#{save_name}"`
File.delete name
when​ 8
`convert "#{name}" -rotate "-90>" "#{save_name}"`
File.delete name
else
File.rename name, save_name
end
end
print ​"​ ​Deleting ​#{thm_names.size}​ THM files: "
thm_names.each ​do​ |name|
print ​'.'
File.delete name
end
# If something bad happened, make sure she
# sees the error message before the window closes.
if​ encountered_error
puts
puts ​"Press [Enter] to finish."
puts
gets
end

Build Your Own Playlist

How you could do it:

# using the shuffle method as defined above
all_oggs = shuffle(Dir[​'**/*.ogg'​])
File.open ​'playlist.m3u'​, ​'w'​ ​do​ |f|
all_oggs.each ​do​ |ogg|
f.write ogg+​"​ ​"
end
end
puts ​'Done!'

And that’s exactly how I’d do it, too.

Build a Better Playlist

How you could do it:

def​ music_shuffle filenames
# We don't want a perfectly random shuffle, so let's
# instead do a shuffle like card-shuffling. Let's
# shuffle the "deck" twice, then cut it once. That's
# not enough times to make a perfect shuffle, but it
# does mix things up a bit.
# Before we do anything, let's actually *sort* the
# input, since we don't know how shuffled it might
# already be, and we don't want it to be *too* random.
filenames = filenames.sort
len = filenames.length
# Now we shuffle twice.
2.times ​do
l_idx = 0 ​# index of next card in left pile
r_idx = len/2 ​# index of next card in right pile
shuf = []
# NOTE: If we have an odd number of "cards",
# then the right pile will be larger.
while​ shuf.length < len
if​ shuf.length%2 == 0
# take card from right pile
shuf.push(filenames[r_idx])
r_idx = r_idx + 1
else
# take card from left pile
shuf.push(filenames[l_idx])
l_idx = l_idx + 1
end
end
filenames = shuf
end
# And cut the deck.
arr = []
cut = rand(len) ​# index of card to cut at
idx = 0
while​ idx < len
arr.push(filenames[(idx+cut)%len])
idx = idx + 1
end
arr
end
songs = [​'aa/bbb'​, ​'aa/ccc'​, ​'aa/ddd'​,
'AAA/xxxx'​, ​'AAA/yyyy'​, ​'AAA/zzzz'​, ​'foo/bar'​]
puts(music_shuffle(songs))
AAA/xxxx
AAA/zzzz
aa/ccc
foo/bar
AAA/yyyy
aa/bbb
aa/ddd

Well, that’s OK, I guess. It’s not all that random, and maybe if you had a larger playlist you’d want to shuffle it three or four times…I don’t really know.

A better way would be mix more carefully and on every level (genre, artist, album). For example, if I have a playlist that is two-thirds lounge and one-third jazz, I want a jazz song roughly every third song (and rarely two in a row and never three in a row). Further, if I had, among all the jazz songs, only two by Kurt Elling (travesty, I know), then one should be somewhere in the first half of the playlist, and the other should be somewhere in the last half. (But where in the respective halves they appear should be truly random.) And all these constraints must be met simultaneously.

What I do is find similar songs (let’s say songs on the same CD), mix them up, and spread them out as far away from each other as I can in the next grouping (say, songs by the same artist). Then I do the same for the next level up (say, genre). The nice thing is that this algorithm is recursive, so I can add levels for free if I want. For example, I have a Billie Holiday CD with multiple recordings of one of the songs. I like it, but I’d like those to be spread out as far from each other as possible in the playlist (while respecting all other constraints at higher levels). No problem—I just make a directory inside the CD directory and move the similar recordings all in there, and the recursion takes care of the rest!

Enough talk; here’s how I would do it:

def​ music_shuffle filenames
songs_and_paths = filenames.map ​do​ |s|
[s, s.split(​'/'​)] ​# [song, path]
end
tree = {:root => []}
# put each song into the tree
insert_into_tree = proc ​do​ |branch, song, path|
if​ path.length == 0 ​# add to current branch
branch[:root] << song
else​ ​# delve deeper
sub_branch = path[0]
path.shift ​# like "pop", but pops off the front
if​ !branch[sub_branch]
branch[sub_branch] = {:root => []}
end
insert_into_tree[branch[sub_branch], song, path]
end
end
songs_and_paths.each{|sp| insert_into_tree[tree, *sp]}
# recursively:
# - shuffle sub-branches (and root)
# - weight each sub-branch (and root)
# - merge (shuffle) these groups together
shuffle_branch = proc ​do​ |branch|
shuffled_subs = []
branch.each ​do​ |key, unshuffled|
shuffled_subs << ​if​ key == :root
unshuffled ​# At this level, these are all duplicates.
else
shuffle_branch[unshuffled]
end
end
weighted_songs = []
shuffled_subs.each ​do​ |shuffled_songs|
shuffled_songs.each_with_index ​do​ |song, idx|
num = shuffled_songs.length.to_f
weight = (idx + rand) / num
weighted_songs << [song, weight]
end
end
weighted_songs.sort_by{|s,v| v}.map{|s,v| s}
end
shuffle_branch[tree]
end
songs = [​'aa/bbb'​, ​'aa/ccc'​, ​'aa/ddd'​,
'AAA/xxxx'​, ​'AAA/yyyy'​, ​'AAA/zzzz'​, ​'foo/bar'​]
puts(music_shuffle(songs))
AAA/zzzz
aa/ccc
foo/bar
aa/bbb
AAA/xxxx
AAA/yyyy
aa/ddd

It might be hard to tell with such a tiny playlist, but with 500 songs you really begin to appreciate how well this method works.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.63.41