dropcaster 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source :rubygems
2
+
3
+ gem 'ruby-mp3info'
4
+ gem 'activesupport'
5
+
6
+ group :development do
7
+ gem "bundler", "~> 1.0.0"
8
+ gem "jeweler", "~> 1.6.4"
9
+ gem 'libxml-ruby'
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,22 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.0.10)
5
+ git (1.2.5)
6
+ jeweler (1.6.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ libxml-ruby (2.2.1)
11
+ rake (0.9.2)
12
+ ruby-mp3info (0.6.15)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ activesupport
19
+ bundler (~> 1.0.0)
20
+ jeweler (~> 1.6.4)
21
+ libxml-ruby
22
+ ruby-mp3info
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Nicolas E. Rabenau
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ Dropcaster - Simple Podcast Publishing with Dropbox
2
+ ===================================================
3
+
4
+ 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.
5
+
6
+ Author: Nicolas E. Rabenau <nerab@gmx.at>
7
+
8
+ What is the problem Dropcaster is trying to solve?
9
+ ==================================================
10
+
11
+ You have a number of podcast episodes that you would like to publish as a feed. Nothing else - no fancy website, no stats, nothing but the pure podcast.
12
+
13
+ With Dropcaster, you simply put the mp3 files into the Public folder of your [Dropbox](http://www.dropbox.com/). Then run the Dropcaster script that generates the feed, writing it to a file in your Dropbox, e.g. index.rss. All mp3 files in the Public folder of your Dropbox are already accessible via HTTP, and so will the RSS file. You can then take the RSS file's URL and publish it (again, this is because any file in the Public folder of my Dropbox automatically gets a public, HTTP-accessible URL).
14
+
15
+ The feed URL can be consumed my any podcatcher, e.g. [iTunes](http://www.apple.com/itunes/) or [Juice](http://juicereceiver.sourceforge.net/).
16
+
17
+ Meta Data
18
+ =========
19
+
20
+ The meta data for your channel is provided in a YAML file. It is expected to be present in the current working directory as channel.yml. This can be overridden using a command line switch.
21
+
22
+ For the podcast episodes, the mp3 files are the authoritative meta data source. Dropcaster reads the metadata from the mp3 files and fills the RSS feed from it.
23
+
24
+ You may override the meta data for any episode by providing a YAML file with the same name as the mp3 file, but with an extension of yml or yaml (ususally refered to as <a href="http://en.wikipedia.org/wiki/Sidecar_file">sidecar file</a>). Any attributes specified in this file override the ID tags in the mp3 file.
25
+
26
+ Please note that Dropcaster will only write the sidecar file if the appropriate option was passed, and it will use the information in it only for generating new files like the index.rss. It will not write back to mp3 files.
27
+
28
+ Use Cases
29
+ =========
30
+
31
+ Publish a New Episode
32
+ ---------------------
33
+
34
+ 1. Drop the mp3 file into the Dropbox Public folder (e.g. ~/Dropbox/Public), and then run the following command in the directory where the mp3 files reside:
35
+
36
+ $ dropcaster > index.rss
37
+
38
+ 1. Dropbox will sync the updated index.rss file to its web server and any podcast client will download the new episode as soon as it has loaded the updated index.rss.
39
+
40
+ Delete an Episode
41
+ -----------------
42
+
43
+ Remove the mp3 you want to delete from the Dropbox Public folder, and then run the following command in the directory where the remaining mp3 files reside:
44
+
45
+ $ dropcaster > index.rss
46
+
47
+ Replace an Episode With an Updated File
48
+ ---------------------------------------
49
+
50
+ In the Dropbox Public folder, replace the mp3 you want to update with a new version, and then run the following command in the directory where the mp3 files reside:
51
+
52
+ $ dropcaster > index.rss
53
+
54
+ Publish Your Feed
55
+ -----------------
56
+
57
+ 1. Re-generate the feed to make sure the it is up to date (see above):
58
+
59
+ $ dropcaster > index.rss
60
+
61
+ 1. In your Dropbox Public folder, right-click the index.rss and select "Dropbox / Copy public link". This copies the public, HTTP-addressable link to your podcast into the clipboard.
62
+ 1. Publish this link and tell people to subscribe to it.
63
+
64
+ Publish More than One Feed
65
+ --------------------------
66
+
67
+ $ dropcaster project1 > project1.rss
68
+ $ dropcaster project2 > project2.rss
69
+
70
+ or
71
+
72
+ $ cd project1
73
+ $ dropcaster > index.rss
74
+ $ cd ../project2
75
+ $ dropcaster > index.rss
76
+
77
+ Include Episodes From Two Subdirectories Into a Single Feed
78
+ -----------------------------------------------------------
79
+
80
+ $ dropcaster project1 project2 > index.rss
81
+
82
+ Episode Identifier (uuid)
83
+ =========================
84
+
85
+ Dropcaster uses a rather simple approach to uniquely identify the episodes. It simply generates a SHA1 hash of the mp3 file. If it changes, for whatever reason (even if only a tag was changes), the episode will get a new UUID, and any podcatcher will fetch the episode again (which is what you want, in most cases).
86
+
87
+ Modifying the sidecar file does not change the UUID, because it only affects the feed and not the episode itself.
88
+
89
+ A Note on iTunes
90
+ ================
91
+
92
+ The generated XML file contains all elements required for iTunes. However, Dropcaster will not notify the iTunes store about new episodes.
93
+
94
+ Using Dropcaster Without Dropbox
95
+ ================================
96
+
97
+ The whole concept of Dropcaster works perfectly fine without Dropbox. Just run the Dropcaster script in a directory of mp3 files and upload the files as well as the generated index.rss to a web server. Leave the relative position of the index and mp3 files as is, otherwise the path to the mp3 files in index.rss will become invalid.
98
+
99
+ Contributing to Dropcaster
100
+ ==========================
101
+
102
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
103
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
104
+ * Fork the project
105
+ * Start a feature/bugfix branch
106
+ * Commit and push until you are happy with your contribution
107
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
108
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
109
+
110
+ Copyright
111
+ =========
112
+
113
+ Copyright (c) 2011 Nicolas E. Rabenau. See LICENSE.txt for further details.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "dropcaster"
18
+ gem.homepage = "http://github.com/nerab/dropcaster"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Simple Podcast Publishing with Dropbox}
21
+ gem.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.}
22
+ gem.email = "nerab@gmx.at"
23
+ gem.authors = ["nerab"]
24
+ gem.executables << 'dropcaster'
25
+ gem.executables << 'lstags'
26
+ # dependencies defined in Gemfile
27
+ end
28
+ Jeweler::RubygemsDotOrgTasks.new
29
+
30
+ require 'rake/testtask'
31
+ Rake::TestTask.new(:test) do |test|
32
+ test.libs << 'lib' << 'test'
33
+ test.pattern = 'test/**/test_*.rb'
34
+ test.verbose = true
35
+ end
36
+
37
+ task :default => :test
38
+
39
+ require 'rake/rdoctask'
40
+ Rake::RDocTask.new do |rdoc|
41
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
42
+
43
+ rdoc.rdoc_dir = 'rdoc'
44
+ rdoc.title = "dropcaster #{version}"
45
+ rdoc.rdoc_files.include('README*')
46
+ rdoc.rdoc_files.include('lib/**/*.rb')
47
+ end
data/TODO ADDED
@@ -0,0 +1,11 @@
1
+ * Allow overriding the path to the ERB template (and document the available template variables)
2
+ * Support a tree of iTunes categories: <itunes:category text="Technology"><itunes:category text="Gadgets"/></itunes:category>
3
+ * If an index.html.erb is present, generate an index.html from it
4
+ ** In addition to that, if an item.html.erb is present, generate individual html for each item from it and link to it from the index.html
5
+ * Print warnings when one of the specs from http://www.apple.com/itunes/podcasts/specs.html is violated
6
+ * Allow muting of the iTunes warnings (see above) using a commandline switch
7
+ * Implement sidecar files
8
+ * Support other file types than mp3
9
+ * Treat lyrics as plain text for the episode page, and with an optional command line switch we could also support markdown etc.
10
+ * Potential optimization for large numbers of mp3 files: If there is an existing index.rss file, do not re-read the information from those files that are still there and still have the same hash value.
11
+ * It feels pretty awkward to refer to the ID3v2 frame names all the time. After all, this is an implementation detail. Maybe we can wrap the frames with a nicer name, but still allow access to the underlying mp3info library? Maybe all we need is some aliasing and / or delegation?
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/dropcaster ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'rubygems'
6
+ require 'yaml'
7
+
8
+ help = <<HELP
9
+ Dropcaster is a podcast feed generator for the command line.
10
+
11
+ Basic Usage:
12
+ dropcaster Prints a podcast feed document for the mp3 files
13
+ in the current directory. The channel definition
14
+ is read from the current directory.
15
+
16
+ dropcaster [<file>...] Prints a podcast feed documentfor the files
17
+ specified as argument. The channel definition
18
+ is read from the current directory.
19
+
20
+ dropcaster [<dir>...] Prints a podcast feed document for the files in
21
+ the directory specified as argument. The channel
22
+ definition is read from the current directory.
23
+
24
+ Options:
25
+ HELP
26
+
27
+ def usage
28
+ "Run '#{File.basename(__FILE__)} --help' for further help."
29
+ end
30
+
31
+ require 'optparse'
32
+ require 'dropcaster'
33
+
34
+ channel_file = 'channel.yml'
35
+
36
+ options = {}
37
+ opts = OptionParser.new do |opts|
38
+ opts.banner = help
39
+
40
+ opts.on("--channel FILE", "Read the channel information from FILE") do |file|
41
+ channel_file = file
42
+ end
43
+
44
+ opts.on("--channel-template FILE", "Use FILE as template for the channel") do |file|
45
+ options[:channel_template] = file
46
+ end
47
+
48
+ opts.on("--version", "Display current version") do
49
+ puts "#{File.basename(__FILE__)} " + Dropcaster::VERSION
50
+ exit 0
51
+ end
52
+ end
53
+
54
+ opts.parse!
55
+
56
+ begin
57
+ options.merge! YAML.load_file(channel_file)
58
+ rescue
59
+ STDERR.puts "Error: Could not find channel definition."
60
+ puts usage
61
+ exit 1
62
+ end
63
+
64
+ if ARGV.empty?
65
+ src_dir = '.'
66
+ else
67
+ src_dir = ARGV
68
+ end
69
+
70
+ begin
71
+ puts Dropcaster::Channel.new(src_dir, options).to_rss
72
+ rescue
73
+ puts "Error: #{$!.message}"
74
+ puts usage
75
+ exit(1)
76
+ end
data/bin/lstags ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless ARGV.size == 1
4
+ STDERR.puts "#{File.basename(__FILE__)}: Missing required parameter for the mp3 file to process"
5
+ exit(1)
6
+ end
7
+
8
+ require 'rubygems'
9
+ require 'mp3info'
10
+
11
+ begin
12
+ Mp3Info.open(ARGV.first) do |mp3info|
13
+ puts 'ID3v1 tags:'
14
+ mp3info.tag.keys.each{|key|
15
+ puts " #{key} => #{mp3info.tag.send(key)}"
16
+ }
17
+ puts
18
+ puts 'ID3v2 tags:'
19
+ mp3info.tag2.keys.each{|key|
20
+ case key
21
+ when 'PIC'
22
+ when 'APIC'
23
+ # picture - do not print binary data
24
+ when 'ULT'
25
+ print " ULT => "
26
+ block_counter = 0
27
+ mp3info.tag2.ULT.bytes{|b|
28
+ print "0x%02x " % b.to_i
29
+ print b > 31 ? " '#{b.chr}' " : " " * 5
30
+ if (block_counter += 1) > 7 # display in blocks of 8 bytes
31
+ puts
32
+ print " " * 9
33
+ block_counter = 0
34
+ end
35
+ }
36
+ puts
37
+ else
38
+ puts " #{key} => #{mp3info.tag2.send(key)}"
39
+ end
40
+ }
41
+ end
42
+ rescue
43
+ puts "Error: #{$!.message}"
44
+ exit(1)
45
+ end
@@ -0,0 +1,65 @@
1
+ require 'erb'
2
+ require 'uri'
3
+
4
+ module Dropcaster
5
+ class Channel < DelegateClass(Hash)
6
+ include HashKeys
7
+
8
+ def initialize(sources, options)
9
+ super(Hash.new)
10
+
11
+ # Assert mantadory options
12
+ [:title, :url, :description].each{|attr|
13
+ raise MissingAttributeError.new(attr) if options[attr].blank?
14
+ }
15
+
16
+ self.merge!(options)
17
+ self.categories = Array.new
18
+ @source_files = Array.new
19
+
20
+ if (sources.respond_to?(:each)) # array
21
+ sources.each{|src|
22
+ add_files(src)
23
+ }
24
+ else
25
+ # single file or directory
26
+ add_files(src)
27
+ end
28
+
29
+ @index_template = ERB.new(File.new(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'channel.rss.erb')), 0, "%<>")
30
+ end
31
+
32
+ def to_rss
33
+ @index_template.result(binding)
34
+ end
35
+
36
+ def items
37
+ all_items = Array.new
38
+ @source_files.each{|src|
39
+ item = Item.new(src)
40
+
41
+ # set author and image_url from channel if empty
42
+ item.tag.artist = self.author if item.artist.blank?
43
+ item.image_url = self.image_url if item.image_url.blank?
44
+
45
+ # Construct absolute URL, based on the channel's enclosure_base attribute
46
+ item.url = enclosure_base || ''
47
+ item.url << '/' unless item.url =~ /\/$/
48
+ item.url += URI.escape(item.file_name, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
49
+
50
+ all_items << item
51
+ }
52
+
53
+ all_items.sort{|x, y| y.pub_date <=> x.pub_date}
54
+ end
55
+
56
+ private
57
+ def add_files(src)
58
+ if File.directory?(src)
59
+ @source_files.concat(Dir.glob(File.join(src, '*.mp3')))
60
+ else
61
+ @source_files << src
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ class ConfigurationError < StandardError
2
+ def initialize(msg)
3
+ super(msg)
4
+ end
5
+ end
6
+
7
+ class MissingAttributeError < ConfigurationError
8
+ def initialize(missingAttribute)
9
+ super("#{missingAttribute} is a mantadory attribute, but it is missing.")
10
+ end
11
+ end
@@ -0,0 +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
@@ -0,0 +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_path).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 ADDED
@@ -0,0 +1,16 @@
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
+
14
+ module Dropcaster
15
+ VERSION = File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION]))
16
+ end
@@ -0,0 +1,57 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- This file was automatically generated by Dropcaster <%= Dropcaster::VERSION%> -->
3
+ <rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0">
4
+ <channel>
5
+ <title><%= title %></title>
6
+ <link><%= url %></link>
7
+ <description><%= description %></description>
8
+ <itunes:summary><%= description %></itunes:summary>
9
+ <% unless language.blank? %>
10
+ <language><%= language %></language>
11
+ <% end %>
12
+ <% unless copyright.blank? %>
13
+ <copyright><%= copyright %></copyright>
14
+ <% end %>
15
+ <% unless subtitle.blank? %>
16
+ <itunes:subtitle><%= subtitle %></itunes:subtitle>
17
+ <% end %>
18
+ <% unless author.blank? %>
19
+ <itunes:author><%= author %></itunes:author>
20
+ <% end %>
21
+ <% unless owner.nil? %>
22
+ <itunes:owner>
23
+ <itunes:name><%= owner[:name] %></itunes:name>
24
+ <itunes:email><%= owner[:email] %></itunes:email>
25
+ </itunes:owner>
26
+ <% end %>
27
+ <% unless image_url.blank? %>
28
+ <itunes:image href="<%= image_url %>"/>
29
+ <% end %>
30
+ <% categories.each{|category| %>
31
+ <itunes:category text="<%= category %>"/>
32
+ <% } %>
33
+ <% unless explicit.blank? %>
34
+ <itunes:explicit><%= explicit %></itunes:explicit>
35
+ <% end %>
36
+ <% items.each{|item| %>
37
+ <item>
38
+ <title><%= item.tag.title || item.tag2.TIT2%></title>
39
+ <itunes:author><%= item.tag2.TP1 || item.tag2.TPE1 %></itunes:author>
40
+ <% unless item.tag2.SUBTITLE.blank? %>
41
+ <itunes:subtitle><%= item.tag2.SUBTITLE %></itunes:subtitle>
42
+ <% end %>
43
+ <% unless item.tag2.TT3.blank? %>
44
+ <itunes:summary><%= item.tag2.TT3 %></itunes:summary>
45
+ <% end %>
46
+ <itunes:image href="<%= item.image_url %>"/>
47
+ <enclosure url="<%= item.url %>" length="<%= item.file_size %>" type="audio/mp3"/>
48
+ <guid isPermaLink="false"><%= item.uuid %></guid>
49
+ <pubDate><%= item.pub_date.to_formatted_s(:rfc822) %></pubDate>
50
+ <itunes:duration><%= item.duration.to_i %></itunes:duration>
51
+ <% unless item.keywords.blank? %>
52
+ <itunes:keywords><%= item.keywords %></itunes:keywords>
53
+ <% end %>
54
+ </item>
55
+ <% } %>
56
+ </channel>
57
+ </rss>
Binary file
Binary file
Binary file
@@ -0,0 +1,14 @@
1
+ :title: 'All About Everything'
2
+ :subtitle: 'A show about everything'
3
+ :url: 'http://www.example.com/podcasts/everything/index.html'
4
+ :enclosure_base: 'http://www.example.com/podcasts/everything'
5
+ :language: 'en-us'
6
+ :copyright: '© 2011 John Doe and Family'
7
+ :author: 'John Doe'
8
+ :description: 'All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our Podcast in the iTunes Store!'
9
+ :owner:
10
+ :name: 'John Doe'
11
+ :email: 'john.doe@example.com'
12
+ :image_url: 'http://example.com/podcasts/everything/AllAboutEverything.jpg'
13
+ :categories: ['Technology', 'Gadgets']
14
+ :explicit: 'No'
Binary file
data/test/helper.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'dropcaster'
@@ -0,0 +1,43 @@
1
+ require 'helper'
2
+ require 'xml/libxml'
3
+
4
+ class TestChannel < Test::Unit::TestCase
5
+ FIXTURES_DIR = File.join(File.dirname(__FILE__), '..', 'fixtures')
6
+ NS_ITUNES = "itunes:http://www.itunes.com/dtds/podcast-1.0.dtd"
7
+
8
+ def setup
9
+ @channel = Dropcaster::Channel.new(FIXTURES_DIR, {:title => 'Test Channel', :url => 'http://www.example.com/podcast.rss', :description => 'A test channel'})
10
+ end
11
+
12
+ def test_items
13
+ assert_equal(1, @channel.items.size)
14
+ assert_equal('77bf84447c0f69ce4a33a18b0ae1e030b82010de', @channel.items.first.uuid)
15
+ end
16
+
17
+ def test_attributes_mantadory
18
+ channel = XML::Document.string(@channel.to_rss).find("//rss/channel").first
19
+ assert_equal('Test Channel', channel.find('title').first.content)
20
+ assert_equal('http://www.example.com/podcast.rss', channel.find('link').first.content)
21
+ assert_equal('A test channel', channel.find('description').first.content)
22
+ end
23
+
24
+ def test_attributes_complete
25
+ options = YAML.load_file(File.join(FIXTURES_DIR, 'test_channel.yml'))
26
+ channel = XML::Document.string(Dropcaster::Channel.new(FIXTURES_DIR, options).to_rss).find("//rss/channel").first
27
+ assert_equal(options[:title], channel.find('title').first.content)
28
+ assert_equal(options[:url], channel.find('link').first.content)
29
+ assert_equal(options[:description], channel.find('description').first.content)
30
+ assert_equal(options[:subtitle], channel.find('itunes:subtitle', NS_ITUNES).first.content)
31
+ assert_equal(options[:language], channel.find('language').first.content)
32
+ assert_equal(options[:copyright], channel.find('copyright').first.content)
33
+ assert_equal(options[:author], channel.find('itunes:author', NS_ITUNES).first.content)
34
+
35
+ owner = channel.find('itunes:owner', NS_ITUNES).first
36
+ assert_equal(options[:owner][:name], owner.find('itunes:name', NS_ITUNES).first.content)
37
+ assert_equal(options[:owner][:email], owner.find('itunes:email', NS_ITUNES).first.content)
38
+
39
+ assert_equal(options[:image_url], channel.find('itunes:image', NS_ITUNES).first['href'])
40
+ # TODO :categories: ['Technology', 'Gadgets']
41
+ assert_equal(options[:explicit], channel.find('itunes:explicit', NS_ITUNES).first.content)
42
+ end
43
+ end
@@ -0,0 +1,61 @@
1
+ require 'helper'
2
+
3
+ class TestItem < Test::Unit::TestCase
4
+ def setup
5
+ @item = Dropcaster::Item.new(File.join(File.dirname(__FILE__), '..', 'fixtures', 'iTunes.mp3'))
6
+ end
7
+
8
+ def test_basics
9
+ assert_in_delta(3, @item.duration, 0.005)
10
+ assert_equal(58119, @item.file_size)
11
+ assert_equal('77bf84447c0f69ce4a33a18b0ae1e030b82010de', @item.uuid)
12
+ assert_equal(1317470900, @item.pub_date.to_i)
13
+ assert_equal(Pathname.new('../../fixtures/iTunes.mp3'), Pathname.new(@item.file_name).relative_path_from(Pathname.new(__FILE__)))
14
+ end
15
+
16
+ def test_tag
17
+ assert_equal('iTunes Artist', @item.tag.artist)
18
+ assert_equal('iTunes Genre', @item.tag.genre_s)
19
+ assert_equal('iTunes Name', @item.tag.title)
20
+ assert_equal(' 00007032 00006EA2 0000A049 00009735 00000559 0000096E 00008000 00008000 00000017 00000017', @item.tag.comments)
21
+ assert_equal('iTunes Album', @item.tag.album)
22
+ assert_equal(1970, @item.tag.year)
23
+ assert_equal(42, @item.tag.tracknum)
24
+ end
25
+
26
+ def test_tag2
27
+ assert_equal('iTunes Artist', @item.tag2.TP1)
28
+ assert_equal('iTunes Genre', @item.tag2.TCO)
29
+ assert_equal('iTunes Name', @item.tag2.TT2)
30
+ assert_equal('iTunes Album', @item.tag2.TAL)
31
+ assert_equal('1970', @item.tag2.TYE)
32
+ assert_equal('iTunes Album Artist', @item.tag2.TP2)
33
+ assert_equal('111', @item.tag2.TBP)
34
+ assert_equal('42/99', @item.tag2.TRK)
35
+ assert_equal('11', @item.tag2.TPA)
36
+ assert_equal('iTunes Grouping', @item.tag2.TT1)
37
+ assert_equal('iTunes Description (Video Pane)', @item.tag2.TT3)
38
+ assert_equal('iTunes Composer', @item.tag2.TCM)
39
+ end
40
+
41
+ def test_lyrics
42
+ assert_equal(1, @item.lyrics.size)
43
+ assert_equal("iTunes Lyrics Line 1\niTunes Lyrics Line 2", @item.lyrics['eng'])
44
+ end
45
+
46
+ def test_comment_remove_itunes_crap
47
+ item = Dropcaster::Item.new(File.join(File.dirname(__FILE__), '..', 'fixtures', 'iTunes.mp3'), {:strip_itunes_private => true})
48
+ assert_equal('iTunes Comments (Info Pane)', item.tag2.COM[0])
49
+ end
50
+
51
+ def test_comment_leave_itunes_crap
52
+ item = Dropcaster::Item.new(File.join(File.dirname(__FILE__), '..', 'fixtures', 'iTunes.mp3'), {:strip_itunes_private => false})
53
+ assert_equal(' 00007032 00006EA2 0000A049 00009735 00000559 0000096E 00008000 00008000 00000017 00000017', item.tag2.COM[0])
54
+ assert_equal('iTunes Comments (Info Pane)', item.tag2.COM[1])
55
+ end
56
+
57
+ def test_tag2_comment
58
+ assert_equal(' 00007032 00006EA2 0000A049 00009735 00000559 0000096E 00008000 00008000 00000017 00000017', @item.tag2.COM[0])
59
+ assert_equal('iTunes Comments (Info Pane)', @item.tag2.COM[1])
60
+ end
61
+ end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dropcaster
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - nerab
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-10-03 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ hash: 3
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ version_requirements: *id001
31
+ name: ruby-mp3info
32
+ prerelease: false
33
+ type: :runtime
34
+ - !ruby/object:Gem::Dependency
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ hash: 3
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ version_requirements: *id002
45
+ name: activesupport
46
+ prerelease: false
47
+ type: :runtime
48
+ - !ruby/object:Gem::Dependency
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ hash: 23
55
+ segments:
56
+ - 1
57
+ - 0
58
+ - 0
59
+ version: 1.0.0
60
+ version_requirements: *id003
61
+ name: bundler
62
+ prerelease: false
63
+ type: :development
64
+ - !ruby/object:Gem::Dependency
65
+ requirement: &id004 !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ hash: 7
71
+ segments:
72
+ - 1
73
+ - 6
74
+ - 4
75
+ version: 1.6.4
76
+ version_requirements: *id004
77
+ name: jeweler
78
+ prerelease: false
79
+ type: :development
80
+ - !ruby/object:Gem::Dependency
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ version_requirements: *id005
91
+ name: libxml-ruby
92
+ prerelease: false
93
+ type: :development
94
+ description: 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.
95
+ email: nerab@gmx.at
96
+ executables:
97
+ - lstags
98
+ - dropcaster
99
+ extensions: []
100
+
101
+ extra_rdoc_files:
102
+ - LICENSE.txt
103
+ - README.md
104
+ - TODO
105
+ files:
106
+ - .document
107
+ - Gemfile
108
+ - Gemfile.lock
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - TODO
113
+ - VERSION
114
+ - bin/dropcaster
115
+ - bin/lstags
116
+ - lib/dropcaster.rb
117
+ - lib/dropcaster/channel.rb
118
+ - lib/dropcaster/errors.rb
119
+ - lib/dropcaster/hashkeys.rb
120
+ - lib/dropcaster/item.rb
121
+ - templates/channel.rss.erb
122
+ - test/fixtures/iTunes.mp3
123
+ - test/fixtures/infoPane.png
124
+ - test/fixtures/lyricsPane.png
125
+ - test/fixtures/test_channel.yml
126
+ - test/fixtures/videoPane.png
127
+ - test/helper.rb
128
+ - test/unit/test_channel.rb
129
+ - test/unit/test_item.rb
130
+ homepage: http://github.com/nerab/dropcaster
131
+ licenses:
132
+ - MIT
133
+ post_install_message:
134
+ rdoc_options: []
135
+
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ hash: 3
144
+ segments:
145
+ - 0
146
+ version: "0"
147
+ required_rubygems_version: !ruby/object:Gem::Requirement
148
+ none: false
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ hash: 3
153
+ segments:
154
+ - 0
155
+ version: "0"
156
+ requirements: []
157
+
158
+ rubyforge_project:
159
+ rubygems_version: 1.8.9
160
+ signing_key:
161
+ specification_version: 3
162
+ summary: Simple Podcast Publishing with Dropbox
163
+ test_files: []
164
+