dropcaster 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -5
- data/Gemfile +11 -11
- data/Gemfile.lock +26 -26
- data/LICENSE.txt +20 -20
- data/README.md +155 -147
- data/Rakefile +47 -47
- data/TODO +14 -13
- data/VERSION +1 -1
- data/bin/dropcaster +117 -111
- data/bin/lstags +45 -45
- data/doc/sample-channel.yml +79 -77
- data/doc/sample-sidecar.yml +19 -19
- data/dropcaster.gemspec +88 -87
- data/lib/dropcaster/channel.rb +96 -86
- data/lib/dropcaster/channel_file_locator.rb +48 -48
- data/lib/dropcaster/errors.rb +25 -19
- data/lib/dropcaster/hashkeys.rb +12 -12
- data/lib/dropcaster/item.rb +43 -43
- data/lib/dropcaster.rb +18 -18
- data/templates/{channel.rss.erb → iTunes.rss.erb} +57 -57
- data/test/extensions/windows.rb +12 -0
- data/test/fixtures/channel.yml +13 -14
- data/test/helper.rb +14 -10
- data/test/unit/test_app.rb +84 -59
- data/test/unit/test_channel.rb +32 -32
- data/test/unit/test_channel_locator.rb +91 -91
- data/test/unit/test_channel_xml.rb +87 -69
- data/test/unit/test_item.rb +63 -61
- metadata +24 -19
data/dropcaster.gemspec
CHANGED
@@ -1,87 +1,88 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in
|
4
|
-
# -*- encoding: utf-8 -*-
|
5
|
-
|
6
|
-
Gem::Specification.new do |s|
|
7
|
-
s.name = %q{dropcaster}
|
8
|
-
s.version = "0.0.
|
9
|
-
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = [
|
12
|
-
s.date = %q{2011-10-
|
13
|
-
s.description = %q{Dropcaster is a podcast feed generator for the command line. It is most simple to use with Dropbox, but works equally well with any other hoster.}
|
14
|
-
s.email = %q{nerab@gmx.at}
|
15
|
-
s.executables = [
|
16
|
-
s.extra_rdoc_files = [
|
17
|
-
"LICENSE.txt",
|
18
|
-
"README.md",
|
19
|
-
"TODO"
|
20
|
-
]
|
21
|
-
s.files = [
|
22
|
-
".document",
|
23
|
-
"Gemfile",
|
24
|
-
"Gemfile.lock",
|
25
|
-
"LICENSE.txt",
|
26
|
-
"README.md",
|
27
|
-
"Rakefile",
|
28
|
-
"TODO",
|
29
|
-
"VERSION",
|
30
|
-
"bin/dropcaster",
|
31
|
-
"bin/lstags",
|
32
|
-
"doc/infoPane.png",
|
33
|
-
"doc/lyricsPane.png",
|
34
|
-
"doc/sample-channel.yml",
|
35
|
-
"doc/sample-sidecar.yml",
|
36
|
-
"doc/videoPane.png",
|
37
|
-
"dropcaster.gemspec",
|
38
|
-
"lib/dropcaster.rb",
|
39
|
-
"lib/dropcaster/channel.rb",
|
40
|
-
"lib/dropcaster/channel_file_locator.rb",
|
41
|
-
"lib/dropcaster/errors.rb",
|
42
|
-
"lib/dropcaster/hashkeys.rb",
|
43
|
-
"lib/dropcaster/item.rb",
|
44
|
-
"templates/
|
45
|
-
"test/
|
46
|
-
"test/fixtures/
|
47
|
-
"test/
|
48
|
-
"test/
|
49
|
-
"test/unit/
|
50
|
-
"test/unit/
|
51
|
-
"test/unit/
|
52
|
-
"test/unit/
|
53
|
-
|
54
|
-
|
55
|
-
s.
|
56
|
-
s.
|
57
|
-
s.
|
58
|
-
s.
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
s.add_runtime_dependency(%q<
|
66
|
-
s.
|
67
|
-
s.add_development_dependency(%q<
|
68
|
-
s.add_development_dependency(%q<
|
69
|
-
s.add_development_dependency(%q<
|
70
|
-
|
71
|
-
|
72
|
-
s.add_dependency(%q<
|
73
|
-
s.add_dependency(%q<
|
74
|
-
s.add_dependency(%q<
|
75
|
-
s.add_dependency(%q<
|
76
|
-
s.add_dependency(%q<
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
s.add_dependency(%q<
|
81
|
-
s.add_dependency(%q<
|
82
|
-
s.add_dependency(%q<
|
83
|
-
s.add_dependency(%q<
|
84
|
-
s.add_dependency(%q<
|
85
|
-
|
86
|
-
end
|
87
|
-
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dropcaster}
|
8
|
+
s.version = "0.0.3"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["nerab"]
|
12
|
+
s.date = %q{2011-10-13}
|
13
|
+
s.description = %q{Dropcaster is a podcast feed generator for the command line. It is most simple to use with Dropbox, but works equally well with any other hoster.}
|
14
|
+
s.email = %q{nerab@gmx.at}
|
15
|
+
s.executables = ["lstags", "dropcaster", "dropcaster", "lstags"]
|
16
|
+
s.extra_rdoc_files = [
|
17
|
+
"LICENSE.txt",
|
18
|
+
"README.md",
|
19
|
+
"TODO"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"LICENSE.txt",
|
26
|
+
"README.md",
|
27
|
+
"Rakefile",
|
28
|
+
"TODO",
|
29
|
+
"VERSION",
|
30
|
+
"bin/dropcaster",
|
31
|
+
"bin/lstags",
|
32
|
+
"doc/infoPane.png",
|
33
|
+
"doc/lyricsPane.png",
|
34
|
+
"doc/sample-channel.yml",
|
35
|
+
"doc/sample-sidecar.yml",
|
36
|
+
"doc/videoPane.png",
|
37
|
+
"dropcaster.gemspec",
|
38
|
+
"lib/dropcaster.rb",
|
39
|
+
"lib/dropcaster/channel.rb",
|
40
|
+
"lib/dropcaster/channel_file_locator.rb",
|
41
|
+
"lib/dropcaster/errors.rb",
|
42
|
+
"lib/dropcaster/hashkeys.rb",
|
43
|
+
"lib/dropcaster/item.rb",
|
44
|
+
"templates/iTunes.rss.erb",
|
45
|
+
"test/extensions/windows.rb",
|
46
|
+
"test/fixtures/channel.yml",
|
47
|
+
"test/fixtures/iTunes.mp3",
|
48
|
+
"test/helper.rb",
|
49
|
+
"test/unit/test_app.rb",
|
50
|
+
"test/unit/test_channel.rb",
|
51
|
+
"test/unit/test_channel_locator.rb",
|
52
|
+
"test/unit/test_channel_xml.rb",
|
53
|
+
"test/unit/test_item.rb"
|
54
|
+
]
|
55
|
+
s.homepage = %q{http://nerab.github.com/dropcaster}
|
56
|
+
s.licenses = ["MIT"]
|
57
|
+
s.require_paths = ["lib"]
|
58
|
+
s.rubygems_version = %q{1.6.1}
|
59
|
+
s.summary = %q{Simple Podcast Publishing with Dropbox}
|
60
|
+
|
61
|
+
if s.respond_to? :specification_version then
|
62
|
+
s.specification_version = 3
|
63
|
+
|
64
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
65
|
+
s.add_runtime_dependency(%q<ruby-mp3info>, [">= 0"])
|
66
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
67
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
68
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
69
|
+
s.add_development_dependency(%q<libxml-ruby>, [">= 0"])
|
70
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
71
|
+
else
|
72
|
+
s.add_dependency(%q<ruby-mp3info>, [">= 0"])
|
73
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
74
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
75
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
76
|
+
s.add_dependency(%q<libxml-ruby>, [">= 0"])
|
77
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
78
|
+
end
|
79
|
+
else
|
80
|
+
s.add_dependency(%q<ruby-mp3info>, [">= 0"])
|
81
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
82
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
83
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
84
|
+
s.add_dependency(%q<libxml-ruby>, [">= 0"])
|
85
|
+
s.add_dependency(%q<rdoc>, [">= 0"])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
data/lib/dropcaster/channel.rb
CHANGED
@@ -1,86 +1,96 @@
|
|
1
|
-
require 'erb'
|
2
|
-
require 'uri'
|
3
|
-
|
4
|
-
module Dropcaster
|
5
|
-
#
|
6
|
-
# Represents a podcast feed in the RSS 2.0 format
|
7
|
-
#
|
8
|
-
class Channel < DelegateClass(Hash)
|
9
|
-
include HashKeys
|
10
|
-
|
11
|
-
# Instantiate a new Channel object. +sources+ must be present and can be a String or Array
|
12
|
-
# of Strings, pointing to a one or more directories or MP3 files.
|
13
|
-
#
|
14
|
-
# +options+ is a hash with all attributes for the channel. The following attributes are
|
15
|
-
# mandatory when a new channel is created:
|
16
|
-
#
|
17
|
-
# * <tt>:title</tt> - Title (name) of the podcast
|
18
|
-
# * <tt>:url</tt> - URL to the podcast
|
19
|
-
# * <tt>:description</tt> - Short description of the podcast (a few words)
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
self.
|
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
|
-
item
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
1
|
+
require 'erb'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Dropcaster
|
5
|
+
#
|
6
|
+
# Represents a podcast feed in the RSS 2.0 format
|
7
|
+
#
|
8
|
+
class Channel < DelegateClass(Hash)
|
9
|
+
include HashKeys
|
10
|
+
|
11
|
+
# Instantiate a new Channel object. +sources+ must be present and can be a String or Array
|
12
|
+
# of Strings, pointing to a one or more directories or MP3 files.
|
13
|
+
#
|
14
|
+
# +options+ is a hash with all attributes for the channel. The following attributes are
|
15
|
+
# mandatory when a new channel is created:
|
16
|
+
#
|
17
|
+
# * <tt>:title</tt> - Title (name) of the podcast
|
18
|
+
# * <tt>:url</tt> - URL to the podcast
|
19
|
+
# * <tt>:description</tt> - Short description of the podcast (a few words)
|
20
|
+
#
|
21
|
+
def initialize(sources, options)
|
22
|
+
super(Hash.new)
|
23
|
+
|
24
|
+
# Assert mandatory options
|
25
|
+
[:title, :url, :description].each{|attr|
|
26
|
+
raise MissingAttributeError.new(attr) if options[attr].blank?
|
27
|
+
}
|
28
|
+
|
29
|
+
self.merge!(options)
|
30
|
+
self.categories = Array.new
|
31
|
+
@source_files = Array.new
|
32
|
+
|
33
|
+
if (sources.respond_to?(:each)) # array
|
34
|
+
sources.each{|src|
|
35
|
+
add_files(src)
|
36
|
+
}
|
37
|
+
else
|
38
|
+
# single file or directory
|
39
|
+
add_files(src)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Prepend the image URL with the channel's base to make an absolute URL
|
43
|
+
self.image_url = URI.join(self.url, self.image_url).to_s unless self.image_url.blank? || self.image_url =~ /^https?:/
|
44
|
+
|
45
|
+
channel_template = self.channel_template || File.join(File.dirname(__FILE__), '..', '..', 'templates', 'iTunes.rss.erb')
|
46
|
+
|
47
|
+
begin
|
48
|
+
@erb_template = ERB.new(File.new(channel_template), 0, "%<>")
|
49
|
+
rescue Errno::ENOENT => e
|
50
|
+
raise TemplateNotFoundError.new(e.message)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Returns this channel as an RSS representation. The actual rendering is done with the help
|
56
|
+
# of an ERB template. By default, it is expected as ../../templates/channel.rss.erb (relative)
|
57
|
+
# to channel.rb.
|
58
|
+
#
|
59
|
+
def to_rss
|
60
|
+
@erb_template.result(binding)
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Returns all items (episodes) of this channel, ordered by newest-first.
|
65
|
+
#
|
66
|
+
def items
|
67
|
+
all_items = Array.new
|
68
|
+
@source_files.each{|src|
|
69
|
+
item = Item.new(src)
|
70
|
+
|
71
|
+
# set author and image_url from channel if empty
|
72
|
+
item.tag.artist = self.author if item.artist.blank?
|
73
|
+
item.image_url = self.image_url if item.image_url.blank?
|
74
|
+
|
75
|
+
# construct absolute URL, based on the channel's enclosures_url attribute
|
76
|
+
# If enclosures_url is not given, take the channel URL as a base.
|
77
|
+
self.enclosures_url = self.url if self.enclosures_url.blank?
|
78
|
+
self.enclosures_url << '/' unless self.enclosures_url =~ /\/$/
|
79
|
+
item.url = URI.join(URI.escape(self.enclosures_url), URI.escape(item.file_name))
|
80
|
+
|
81
|
+
all_items << item
|
82
|
+
}
|
83
|
+
|
84
|
+
all_items.sort{|x, y| y.pub_date <=> x.pub_date}
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def add_files(src)
|
89
|
+
if File.directory?(src)
|
90
|
+
@source_files.concat(Dir.glob(File.join(src, '*.mp3')))
|
91
|
+
else
|
92
|
+
@source_files << src
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -1,48 +1,48 @@
|
|
1
|
-
module Dropcaster
|
2
|
-
#
|
3
|
-
# Encapsulates the strategy how to find the channel definition file
|
4
|
-
#
|
5
|
-
class ChannelFileLocator
|
6
|
-
class << self
|
7
|
-
#
|
8
|
-
# Locates the channel definition file based on the <tt>sources</tt> directory.
|
9
|
-
#
|
10
|
-
# * If <tt>sources</tt> is a single file name, the channel definition file is expected
|
11
|
-
# as channel.yml in the same directory.
|
12
|
-
#
|
13
|
-
# * If <tt>sources</tt> is a single directory name, the channel definition file is
|
14
|
-
# expected as channel.yml in that directory.
|
15
|
-
#
|
16
|
-
# * If <tt>sources</tt> is an array of file names, the channel definition file is
|
17
|
-
# expected as channel.yml in the directory common to all files. If the files are
|
18
|
-
# located in more than one directory, an AmbiguousSourcesError is raised. In that
|
19
|
-
# case, the caller should specify the channel.yml as command line parameter.
|
20
|
-
#
|
21
|
-
# * If <tt>sources</tt> is an array with more than a single directory name, an
|
22
|
-
# AmbiguousSourcesError is raised. In that case, the caller should specify the
|
23
|
-
# channel.yml as command line parameter.
|
24
|
-
#
|
25
|
-
def locate(sources)
|
26
|
-
channel_source_dir = nil
|
27
|
-
|
28
|
-
if sources.respond_to?(:at)
|
29
|
-
# More than one source given. Check that they are all in the same directory.
|
30
|
-
distinct_dirs = sources.collect{|source| File.dirname(source)}.uniq
|
31
|
-
|
32
|
-
if 1 == distinct_dirs.size
|
33
|
-
# If all are the in same directory, use that as source directory where channel.yml is expected.
|
34
|
-
channel_source_dir = distinct_dirs.first
|
35
|
-
else
|
36
|
-
# Since no channel_file was specified at the command line, throw and quit
|
37
|
-
raise AmbiguousSourcesError.new(sources)
|
38
|
-
end
|
39
|
-
else
|
40
|
-
# If a single file or directory is given, use that as source directory where channel.yml is expected.
|
41
|
-
channel_source_dir = File.dirname(sources)
|
42
|
-
end
|
43
|
-
|
44
|
-
File.join(channel_source_dir, CHANNEL_YML)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
1
|
+
module Dropcaster
|
2
|
+
#
|
3
|
+
# Encapsulates the strategy how to find the channel definition file
|
4
|
+
#
|
5
|
+
class ChannelFileLocator
|
6
|
+
class << self
|
7
|
+
#
|
8
|
+
# Locates the channel definition file based on the <tt>sources</tt> directory.
|
9
|
+
#
|
10
|
+
# * If <tt>sources</tt> is a single file name, the channel definition file is expected
|
11
|
+
# as channel.yml in the same directory.
|
12
|
+
#
|
13
|
+
# * If <tt>sources</tt> is a single directory name, the channel definition file is
|
14
|
+
# expected as channel.yml in that directory.
|
15
|
+
#
|
16
|
+
# * If <tt>sources</tt> is an array of file names, the channel definition file is
|
17
|
+
# expected as channel.yml in the directory common to all files. If the files are
|
18
|
+
# located in more than one directory, an AmbiguousSourcesError is raised. In that
|
19
|
+
# case, the caller should specify the channel.yml as command line parameter.
|
20
|
+
#
|
21
|
+
# * If <tt>sources</tt> is an array with more than a single directory name, an
|
22
|
+
# AmbiguousSourcesError is raised. In that case, the caller should specify the
|
23
|
+
# channel.yml as command line parameter.
|
24
|
+
#
|
25
|
+
def locate(sources)
|
26
|
+
channel_source_dir = nil
|
27
|
+
|
28
|
+
if sources.respond_to?(:at)
|
29
|
+
# More than one source given. Check that they are all in the same directory.
|
30
|
+
distinct_dirs = sources.collect{|source| File.dirname(source)}.uniq
|
31
|
+
|
32
|
+
if 1 == distinct_dirs.size
|
33
|
+
# If all are the in same directory, use that as source directory where channel.yml is expected.
|
34
|
+
channel_source_dir = distinct_dirs.first
|
35
|
+
else
|
36
|
+
# Since no channel_file was specified at the command line, throw and quit
|
37
|
+
raise AmbiguousSourcesError.new(sources)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
# If a single file or directory is given, use that as source directory where channel.yml is expected.
|
41
|
+
channel_source_dir = File.dirname(sources)
|
42
|
+
end
|
43
|
+
|
44
|
+
File.join(channel_source_dir, CHANNEL_YML)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/dropcaster/errors.rb
CHANGED
@@ -1,19 +1,25 @@
|
|
1
|
-
module Dropcaster
|
2
|
-
class ConfigurationError < StandardError
|
3
|
-
def initialize(msg)
|
4
|
-
super(msg)
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
class MissingAttributeError < ConfigurationError
|
9
|
-
def initialize(missingAttribute)
|
10
|
-
super("#{missingAttribute} is a mandatory attribute, but it is missing.")
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class AmbiguousSourcesError < ConfigurationError
|
15
|
-
def initialize(ambiguousSources)
|
16
|
-
super("The list of sources is ambiguous. Can't derive common directory from these: #{ambiguousSources.inspect}")
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
1
|
+
module Dropcaster
|
2
|
+
class ConfigurationError < StandardError
|
3
|
+
def initialize(msg)
|
4
|
+
super(msg)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class MissingAttributeError < ConfigurationError
|
9
|
+
def initialize(missingAttribute)
|
10
|
+
super("#{missingAttribute} is a mandatory attribute, but it is missing.")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AmbiguousSourcesError < ConfigurationError
|
15
|
+
def initialize(ambiguousSources)
|
16
|
+
super("The list of sources is ambiguous. Can't derive common directory from these: #{ambiguousSources.inspect}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class TemplateNotFoundError < ConfigurationError
|
21
|
+
def initialize(message)
|
22
|
+
super("Unable to load template file: #{message}")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/dropcaster/hashkeys.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
module Dropcaster
|
2
|
-
module HashKeys
|
3
|
-
def method_missing(meth,*args)
|
4
|
-
m = meth.id2name
|
5
|
-
if /=$/ =~ m
|
6
|
-
self[m.chop.to_sym] = (args.length < 2 ? args[0] : args)
|
7
|
-
else
|
8
|
-
self[m.to_sym]
|
9
|
-
end
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
1
|
+
module Dropcaster
|
2
|
+
module HashKeys
|
3
|
+
def method_missing(meth,*args)
|
4
|
+
m = meth.id2name
|
5
|
+
if /=$/ =~ m
|
6
|
+
self[m.chop.to_sym] = (args.length < 2 ? args[0] : args)
|
7
|
+
else
|
8
|
+
self[m.to_sym]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/dropcaster/item.rb
CHANGED
@@ -1,43 +1,43 @@
|
|
1
|
-
require 'mp3info'
|
2
|
-
require 'digest/sha1'
|
3
|
-
|
4
|
-
module Dropcaster
|
5
|
-
class Item < DelegateClass(Hash)
|
6
|
-
include HashKeys
|
7
|
-
|
8
|
-
def initialize(file_path, options = nil)
|
9
|
-
super(Hash.new)
|
10
|
-
|
11
|
-
Mp3Info.open(file_path){|mp3info|
|
12
|
-
self[:file_name] = Pathname.new(File.expand_path(file_path)).relative_path_from(Pathname.new(Dir.pwd)).cleanpath.to_s
|
13
|
-
self[:tag] = mp3info.tag
|
14
|
-
self[:tag2] = mp3info.tag2
|
15
|
-
self[:duration] = mp3info.length
|
16
|
-
}
|
17
|
-
|
18
|
-
self[:file_size] = File.new(self.file_name).stat.size
|
19
|
-
self[:uuid] = Digest::SHA1.hexdigest(File.read(self.file_name))
|
20
|
-
|
21
|
-
if self.tag2.TDR.blank?
|
22
|
-
self[:pub_date] = DateTime.parse(File.new(self.file_name).mtime.to_s)
|
23
|
-
else
|
24
|
-
self[:pub_date] = DateTime.parse(self.tag2.TDR)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Remove iTunes normalization crap (if configured)
|
28
|
-
self.tag2.COM.delete_if{|comment|
|
29
|
-
comment =~ /^( [0-9A-F]{8}){10}$/
|
30
|
-
} if options && options[:strip_itunes_private]
|
31
|
-
|
32
|
-
# Convert lyrics frame into a hash, keyed by the three-letter language code
|
33
|
-
if tag2.ULT
|
34
|
-
lyrics_parts = tag2.ULT.split(0.chr)
|
35
|
-
|
36
|
-
if lyrics_parts && 3 == lyrics_parts.size
|
37
|
-
self.lyrics = Hash.new
|
38
|
-
self.lyrics[lyrics_parts[1]] = lyrics_parts[2]
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
1
|
+
require 'mp3info'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Dropcaster
|
5
|
+
class Item < DelegateClass(Hash)
|
6
|
+
include HashKeys
|
7
|
+
|
8
|
+
def initialize(file_path, options = nil)
|
9
|
+
super(Hash.new)
|
10
|
+
|
11
|
+
Mp3Info.open(file_path){|mp3info|
|
12
|
+
self[:file_name] = Pathname.new(File.expand_path(file_path)).relative_path_from(Pathname.new(Dir.pwd)).cleanpath.to_s
|
13
|
+
self[:tag] = mp3info.tag
|
14
|
+
self[:tag2] = mp3info.tag2
|
15
|
+
self[:duration] = mp3info.length
|
16
|
+
}
|
17
|
+
|
18
|
+
self[:file_size] = File.new(self.file_name).stat.size
|
19
|
+
self[:uuid] = Digest::SHA1.hexdigest(File.read(self.file_name))
|
20
|
+
|
21
|
+
if self.tag2.TDR.blank?
|
22
|
+
self[:pub_date] = DateTime.parse(File.new(self.file_name).mtime.to_s)
|
23
|
+
else
|
24
|
+
self[:pub_date] = DateTime.parse(self.tag2.TDR)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Remove iTunes normalization crap (if configured)
|
28
|
+
self.tag2.COM.delete_if{|comment|
|
29
|
+
comment =~ /^( [0-9A-F]{8}){10}$/
|
30
|
+
} if options && options[:strip_itunes_private]
|
31
|
+
|
32
|
+
# Convert lyrics frame into a hash, keyed by the three-letter language code
|
33
|
+
if tag2.ULT
|
34
|
+
lyrics_parts = tag2.ULT.split(0.chr)
|
35
|
+
|
36
|
+
if lyrics_parts && 3 == lyrics_parts.size
|
37
|
+
self.lyrics = Hash.new
|
38
|
+
self.lyrics[lyrics_parts[1]] = lyrics_parts[2]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/dropcaster.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
|
-
$:.unshift File.dirname(__FILE__)
|
2
|
-
|
3
|
-
require 'bundler/setup'
|
4
|
-
require 'delegate'
|
5
|
-
require 'yaml'
|
6
|
-
require 'active_support/core_ext/date_time/conversions'
|
7
|
-
require 'active_support/core_ext/object/blank'
|
8
|
-
|
9
|
-
require 'dropcaster/errors'
|
10
|
-
require 'dropcaster/hashkeys'
|
11
|
-
require 'dropcaster/channel'
|
12
|
-
require 'dropcaster/item'
|
13
|
-
require 'dropcaster/channel_file_locator'
|
14
|
-
|
15
|
-
module Dropcaster
|
16
|
-
VERSION = File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION]))
|
17
|
-
CHANNEL_YML = 'channel.yml'
|
18
|
-
end
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'delegate'
|
5
|
+
require 'yaml'
|
6
|
+
require 'active_support/core_ext/date_time/conversions'
|
7
|
+
require 'active_support/core_ext/object/blank'
|
8
|
+
|
9
|
+
require 'dropcaster/errors'
|
10
|
+
require 'dropcaster/hashkeys'
|
11
|
+
require 'dropcaster/channel'
|
12
|
+
require 'dropcaster/item'
|
13
|
+
require 'dropcaster/channel_file_locator'
|
14
|
+
|
15
|
+
module Dropcaster
|
16
|
+
VERSION = File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION]))
|
17
|
+
CHANNEL_YML = 'channel.yml'
|
18
|
+
end
|