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 |
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.
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.
18.224.63.41