Created
April 20, 2013 08:40
-
-
Save mschuerig/5425277 to your computer and use it in GitHub Desktop.
Generate yearly and year-monthly playlists based on when albums were added to the collection. The heuristic used to find the adding date is to take the date of the oldest file in the album's directory. No CLI yet, use at your own risk.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/ruby1.9.3 | |
require 'taglib' | |
require 'debugger' | |
# TODO | |
# - CLI | |
# - -m, --monthly | |
# - -u, --update | |
# - -q, --quiet | |
# - -v, --verbose | |
# - 1999,2002-2005,2008 | |
# - --current (update current year and month) | |
$quiet = false | |
$verbose = true | |
$update = true | |
class BottomUpDirectories | |
include Enumerable | |
def self.find(*dirs, &block) | |
if block | |
new(*dirs).each(&block) | |
else | |
new(*dirs) | |
end | |
end | |
def initialize(*dirs) | |
options = dirs.last.is_a?(Hash) ? dirs.pop : {} | |
@dirs = dirs | |
@excludes = Array(options[:exclude]) | |
@leaves_only = options[:leaves_only] | |
end | |
def each(&block) | |
@dirs.each do |d| | |
root_re = %r{^#{d}/} | |
each_dir([d], root_re, &block) | |
end | |
end | |
private | |
MATCH_FLAGS = File::FNM_PATHNAME | File::FNM_DOTMATCH | |
def each_dir(dirs, root_re, &block) | |
dirs.each do |dir| | |
begin | |
children = [] | |
Dir.open(dir) do |d| | |
d.each do |it| | |
next if it == '.' || it == '..' | |
path = File.join(dir, it) | |
next unless File.exist?(path) && File.lstat(path).directory? | |
unless @excludes.empty? | |
match_path = path.sub(root_re, '') | |
next if @excludes.any? { |excl| File.fnmatch(excl, match_path, MATCH_FLAGS) } | |
end | |
children << path | |
end | |
end | |
each_dir(children, root_re, &block) | |
block[dir] unless @leaves_only && !children.empty? | |
rescue Errno::ENOENT, Errno::EACCES | |
### really? | |
end | |
end | |
end | |
end | |
class Album | |
include Enumerable | |
def initialize(dir) | |
@dir = dir | |
end | |
def each(&block) | |
tracks.each(&block) | |
end | |
def tracks | |
track_files.map { |tf| Track.new(File.join(@dir, tf)) }.sort_by(&:order) | |
end | |
private | |
def track_files | |
Dir.open(@dir) do |d| | |
d.select do |f| | |
f =~ /\A[^.].*\.(:?aac|flac|mp3|ogg)\z/ | |
end | |
end | |
end | |
end | |
class Track | |
attr_reader :file | |
def initialize(file) | |
@file = file | |
end | |
def order | |
if File.basename(@file) =~ /^\d/ | |
@file | |
else | |
TagLib::FileRef.open(@file) { |ref| | |
ref.tag && ref.tag.track | |
} || @file | |
@file | |
end | |
end | |
def to_s | |
@file.sub(%r{\./}, '') | |
end | |
end | |
class Playlist | |
attr_reader :root_dir, :playlist_file | |
def initialize(name, root_dir = nil) | |
@root_dir = root_dir || '.' | |
@playlist_file = File.join(@root_dir, 'yearly', name) | |
end | |
def self.open(name, root_dir = nil, &block) | |
new(name, root_dir).open(&block) | |
end | |
def exist? | |
File.exist?(playlist_file) | |
end | |
def open | |
with_lazy_stream do | |
yield self | |
end | |
end | |
def write_album(album) | |
with_stream do |stream| | |
stream.puts "# #{album}" | |
Album.new(album_dir(album)).tap do |a| | |
a.each do |track| | |
stream.puts track | |
end | |
end | |
end | |
end | |
private | |
def album_dir(album) | |
File.join(@root_dir, album) | |
end | |
def with_lazy_stream | |
yield | |
ensure | |
close_stream | |
end | |
def with_stream | |
@stream ||= File.open(playlist_file, "w") | |
yield(@stream) | |
end | |
def close_stream | |
@stream.close if @stream | |
@steam = nil | |
end | |
end | |
class Grouper | |
def initialize(*parts) | |
@parts = parts | |
end | |
def group(date) | |
@parts.map { |p| date.public_send(p) } | |
end | |
def playlist(group) | |
"#{group.join('-')}.m3u" | |
end | |
end | |
BY_YEAR = Grouper.new(:year) | |
BY_MONTH = Grouper.new(:year, :month) | |
@grouper = BY_YEAR | |
def albums_and_times | |
BottomUpDirectories.find('.', leaves_only: true).map do |d| | |
Dir.open(d) do |dir| | |
oldest = dir.map do |e| | |
next if e == '..' | |
File.mtime(File.join(dir, e)) | |
end.compact.sort.first | |
d.sub!(%r{\./}, '') | |
[d, oldest] if oldest | |
end | |
end.compact | |
end | |
grouped_albums = albums_and_times.group_by do |album, time| | |
@grouper.group(time) | |
end | |
grouped_albums.sort_by(&:first).each do |group, albums| | |
puts "*** #{group} (#{albums.size})" unless $quiet | |
Playlist.open(@grouper.playlist(group)) do |playlist| | |
break if playlist.exist? && !$update | |
albums.sort_by(&:last).each do |album, _| | |
puts album if $verbose | |
playlist.write_album(album) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment