gst-kitchen 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -4
- data/bin/gst-kitchen +49 -2
- data/gst-kitchen.gemspec +2 -1
- data/lib/gst-kitchen.rb +1 -0
- data/lib/gst-kitchen/episode.rb +1 -3
- data/lib/gst-kitchen/feed.rb +71 -0
- data/lib/gst-kitchen/podcast.rb +9 -7
- data/lib/gst-kitchen/version.rb +1 -1
- data/templates/episodes.rss.erb +22 -22
- metadata +22 -5
data/README.md
CHANGED
@@ -35,7 +35,7 @@ gst-kitchen assumes various conventions about (file) naming and URLs.
|
|
35
35
|
|
36
36
|
Meta data from each episodes is read from your Auphonic production. The episode number is read from the `title`,
|
37
37
|
which must start with `<handle><number>`. The episode name is read from `subtitle`, summary and description is
|
38
|
-
set to Auphonic's `summary` field.
|
38
|
+
set to Auphonic's `summary` field. File sizes, playtime etc. are also read from the production.
|
39
39
|
|
40
40
|
|
41
41
|
## Usage
|
@@ -48,8 +48,8 @@ Then you need to create a `podcast.yml` containing your podcast metadata.
|
|
48
48
|
You can take a look at https://github.com/tisba/gst-website/blob/master/podcast.yml as an example.
|
49
49
|
Important fields are
|
50
50
|
|
51
|
-
* basic meta data like, `title`, `
|
52
|
-
* `handle` is a short *handle* for your podcast. It can be an abbreviation
|
51
|
+
* basic meta data like, `title`, `subtitle`, `author`, `email` and `language`
|
52
|
+
* `handle` is a short *handle* for your podcast. It can be an abbreviation, acronym, or anything you like. For geekstammtisch it's `GST`.
|
53
53
|
* base URLs for the website and media location
|
54
54
|
* list of available audio formats: fileext_encoding, e.g. `m4a_aac` or `mp3_mp3`
|
55
55
|
* `rss_output_path` specifies where the generated feeds will be located
|
@@ -84,4 +84,4 @@ Render all configured feeds based on all episodes in `episodes/`
|
|
84
84
|
5. Create new Pull Request
|
85
85
|
|
86
86
|
|
87
|
-
[igor]: https://github.com/m4p/fanboys-IGOR "IGOR"
|
87
|
+
[igor]: https://github.com/m4p/fanboys-IGOR "IGOR"
|
data/bin/gst-kitchen
CHANGED
@@ -9,7 +9,7 @@ require "fileutils"
|
|
9
9
|
|
10
10
|
SUB_COMMANDS = %w(feed process list version)
|
11
11
|
global_opts = Trollop::options do
|
12
|
-
banner "
|
12
|
+
banner "Geekstammtischs Kitchen to publish podcats..."
|
13
13
|
opt :podcast, "path of podcast YAML specification", :default => "podcast.yml"
|
14
14
|
stop_on SUB_COMMANDS
|
15
15
|
end
|
@@ -17,10 +17,12 @@ end
|
|
17
17
|
case ARGV.shift
|
18
18
|
when "version"
|
19
19
|
puts GstKitchen::VERSION
|
20
|
+
|
20
21
|
when "feed"
|
21
22
|
subcmd_opts = Trollop::options do
|
22
23
|
end
|
23
24
|
Podcast.from_yaml(global_opts[:podcast]).render_all_feeds
|
25
|
+
|
24
26
|
when "list"
|
25
27
|
productions = Auphonic::Account.init_from_system.productions
|
26
28
|
productions.each do |production|
|
@@ -33,13 +35,58 @@ when "process"
|
|
33
35
|
end
|
34
36
|
production = Auphonic::Account.init_from_system.production(subcmd_opts[:uuid])
|
35
37
|
|
36
|
-
podcast = Podcast.from_yaml(
|
38
|
+
podcast = Podcast.from_yaml(global_opts[:podcast])
|
37
39
|
FileUtils.mkdir_p podcast.episodes_path
|
38
40
|
episode = podcast.create_episode_from_auphonic production
|
39
41
|
|
40
42
|
puts "Episode: #{episode}"
|
41
43
|
puts "Writing episode YAML to #{File.join(podcast.episodes_path, episode.handle.downcase)}.yml"
|
42
44
|
podcast.export_episode(episode)
|
45
|
+
|
46
|
+
when "stats"
|
47
|
+
podcast = Podcast.from_yaml(global_opts[:podcast])
|
48
|
+
if STDIN.tty?
|
49
|
+
STDERR.puts "You need to pipe logs to gst-kitchen stats, e.g. cat logs/* | gst-kitchen stats"
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
downloads = {}
|
53
|
+
while (logline = STDIN.gets)
|
54
|
+
# 93.194.154.91 - - [13/Dec/2012:16:22:38 +0100] "GET /episodes/gst000.m4a HTTP/1.1" 200 18676513 "-" "iTunes/11.0 (Macintosh; OS X 10.8.2) AppleWebKit/536.26.14"
|
55
|
+
|
56
|
+
/ - - \[(?<date>.*?)\].*?(?<filename>gst\d{3}\....).*?2\d\d (?<size>\d+) / =~ logline
|
57
|
+
|
58
|
+
next unless filename
|
59
|
+
|
60
|
+
handle, format = filename.split(".")
|
61
|
+
|
62
|
+
next unless episode = podcast.episode_by_handle(handle)
|
63
|
+
next unless ["mp3", "m4a"].include? format
|
64
|
+
|
65
|
+
downloads[handle] ||= {}
|
66
|
+
downloads[handle][format] ||= {}
|
67
|
+
|
68
|
+
media_format = format == "mp3" ? "mp3" : "aac"
|
69
|
+
|
70
|
+
downloads[handle][format][:size] ||= episode.media[media_format]["size"]
|
71
|
+
downloads[handle][format][:downloaded] ||= 0
|
72
|
+
downloads[handle][format][:downloaded] += size.to_i
|
73
|
+
end
|
74
|
+
|
75
|
+
total_downloads = {}
|
76
|
+
downloads.each do |handle, episode|
|
77
|
+
episode.each do |format, stats|
|
78
|
+
downloads[handle][format][:count] = stats[:downloaded].to_f / stats[:size]
|
79
|
+
total_downloads[handle] ||= {}
|
80
|
+
total_downloads[handle]["total_downloads"] ||= 0
|
81
|
+
total_downloads[handle]["total_downloads"] += downloads[handle][format][:count]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
downloads.keys.each do |handle|
|
86
|
+
downloads[handle].merge!(total_downloads[handle])
|
87
|
+
end
|
88
|
+
|
89
|
+
puts downloads.to_yaml
|
43
90
|
else
|
44
91
|
Trollop::die "unknown subcommand #{subcmd.inspect}"
|
45
92
|
end
|
data/gst-kitchen.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |gem|
|
|
10
10
|
gem.email = ["sebastian.cohnen@gmx.net"]
|
11
11
|
gem.description = %q{gst-kitchen is a gem to publish podcasts like a nerd with auphonic!}
|
12
12
|
gem.summary = %q{gst-kitchen is a gem to publish podcasts like a nerd with auphonic!}
|
13
|
-
gem.homepage = ""
|
13
|
+
gem.homepage = "http://github.com/tisba/gst-kitchen"
|
14
14
|
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
@@ -20,4 +20,5 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.add_dependency "yajl-ruby"
|
21
21
|
gem.add_dependency "trollop"
|
22
22
|
gem.add_dependency "rake"
|
23
|
+
gem.add_dependency "redcarpet"
|
23
24
|
end
|
data/lib/gst-kitchen.rb
CHANGED
data/lib/gst-kitchen/episode.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
class Episode < Struct.new(:number, :name, :length, :media, :auphonic_uuid, :published_at, :summary
|
1
|
+
class Episode < Struct.new(:number, :name, :length, :media, :auphonic_uuid, :published_at, :summary)
|
2
2
|
include Comparable
|
3
3
|
|
4
4
|
def self.from_auphonic(production)
|
@@ -11,7 +11,6 @@ class Episode < Struct.new(:number, :name, :length, :media, :auphonic_uuid, :pub
|
|
11
11
|
length: data["data"]["length"],
|
12
12
|
name: data["data"]["metadata"]["subtitle"].strip,
|
13
13
|
summary: data["data"]["metadata"]["summary"].strip,
|
14
|
-
description: data["data"]["metadata"]["summary"].strip
|
15
14
|
}
|
16
15
|
|
17
16
|
media = data["data"]["output_files"].each_with_object({}) do |item, obj|
|
@@ -29,7 +28,6 @@ class Episode < Struct.new(:number, :name, :length, :media, :auphonic_uuid, :pub
|
|
29
28
|
episode.auphonic_uuid = metadata[:auphonic_uuid]
|
30
29
|
episode.published_at = Time.now
|
31
30
|
episode.summary = metadata[:summary]
|
32
|
-
episode.description = metadata[:description]
|
33
31
|
episode.media = media
|
34
32
|
|
35
33
|
episode
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
require 'ruby-debug'
|
3
|
+
|
4
|
+
class GstKitchen::Feed
|
5
|
+
|
6
|
+
class ShownotesRenderer < Redcarpet::Render::HTML
|
7
|
+
|
8
|
+
TWITTER_HANDLE_PATTER = /(\W)@(\w+)\b/
|
9
|
+
|
10
|
+
def preprocess(full_document)
|
11
|
+
return full_document if full_document.nil?
|
12
|
+
|
13
|
+
# Duplicate the string so we don't alter the original, then call to_str
|
14
|
+
# to cast it back to a String instead of a SafeBuffer. This is required
|
15
|
+
# for gsub calls to work as we need them to.
|
16
|
+
full_document = full_document.dup.to_str
|
17
|
+
|
18
|
+
# Extract pre blocks so they are not altered
|
19
|
+
# from http://github.github.com/github-flavored-markdown/
|
20
|
+
extractions = {}
|
21
|
+
full_document.gsub!(%r{<pre>.*?</pre>|<code>.*?</code>}m) do |match|
|
22
|
+
md5 = Digest::MD5.hexdigest(match)
|
23
|
+
extractions[md5] = match
|
24
|
+
"{gfm-extraction-#{md5}}"
|
25
|
+
end
|
26
|
+
|
27
|
+
full_document = parse(full_document)
|
28
|
+
|
29
|
+
# Insert pre block extractions
|
30
|
+
full_document.gsub!(/\{gfm-extraction-(\h{32})\}/) do
|
31
|
+
extractions[$1]
|
32
|
+
end
|
33
|
+
|
34
|
+
return full_document
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def parse(document)
|
40
|
+
parse_twitter_handle(document)
|
41
|
+
|
42
|
+
return document
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_twitter_handle(document)
|
46
|
+
document.gsub!(TWITTER_HANDLE_PATTER, '\1[@\2](https://twitter.com/\2)')
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
TEMPLATE_PATH = File.join(File.dirname(__FILE__), "..", "..", "templates")
|
52
|
+
|
53
|
+
attr_reader :format, :template
|
54
|
+
|
55
|
+
def initialize(options = {})
|
56
|
+
@format = options[:format]
|
57
|
+
@template = ERB.new(File.read(File.join(TEMPLATE_PATH, "#{options[:template]}.rss.erb")))
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_as_markdown(text)
|
61
|
+
markdown = Redcarpet::Markdown.new(ShownotesRenderer, autolink: true)
|
62
|
+
markdown.render text
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_xml(variables = {})
|
66
|
+
variables.each { |var, value| instance_variable_set("@#{var}", value) }
|
67
|
+
|
68
|
+
@template.result(binding)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/gst-kitchen/podcast.rb
CHANGED
@@ -50,6 +50,12 @@ class Podcast
|
|
50
50
|
Episode.from_auphonic production
|
51
51
|
end
|
52
52
|
|
53
|
+
def episode_by_handle(handle)
|
54
|
+
self.episodes.find do |episode|
|
55
|
+
episode.handle.downcase == handle.downcase
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
53
59
|
def feed_url(format)
|
54
60
|
url = URI(self.website)
|
55
61
|
url.path = "/#{rss_file(format)}"
|
@@ -78,15 +84,10 @@ class Podcast
|
|
78
84
|
self
|
79
85
|
end
|
80
86
|
|
81
|
-
def current_format
|
82
|
-
@current_format
|
83
|
-
end
|
84
|
-
|
85
87
|
def render_feed(format)
|
86
|
-
|
87
|
-
template = ERB.new File.read(File.join(File.dirname(__FILE__), "../..", "templates/episodes.rss.erb"))
|
88
|
+
feed = GstKitchen::Feed.new format: format, template: "episodes"
|
88
89
|
File.open(File.join(rss_output_path, rss_file(format)), "w") do |rss|
|
89
|
-
rss.write
|
90
|
+
rss.write feed.to_xml(podcast: podcast)
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
@@ -106,6 +107,7 @@ class Podcast
|
|
106
107
|
end
|
107
108
|
|
108
109
|
private
|
110
|
+
|
109
111
|
def rss_file(format)
|
110
112
|
"episodes.#{format.file_ext}.rss"
|
111
113
|
end
|
data/lib/gst-kitchen/version.rb
CHANGED
data/templates/episodes.rss.erb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
2
2
|
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
|
3
3
|
<channel>
|
4
|
-
<title><%= podcast.title %></title>
|
5
|
-
<link><%= podcast.website %></link>
|
6
|
-
<atom:link rel="self" href="<%= podcast.feed_url(
|
7
|
-
<% podcast.other_formats(
|
8
|
-
<atom:link rel="alternate" href="<%= podcast.feed_url(format) %>" type="application/rss+xml" title="<%= format.file_ext.upcase %> Audio"/>
|
4
|
+
<title><%= @podcast.title %></title>
|
5
|
+
<link><%= @podcast.website %></link>
|
6
|
+
<atom:link rel="self" href="<%= @podcast.feed_url(@format) %>" type="application/rss+xml" title="<%= @format.file_ext.upcase %> Audio"/>
|
7
|
+
<% @podcast.other_formats(@format).each do |format| %>
|
8
|
+
<atom:link rel="alternate" href="<%= @podcast.feed_url(format) %>" type="application/rss+xml" title="<%= format.file_ext.upcase %> Audio"/>
|
9
9
|
<% end %>
|
10
|
-
<language><%= podcast.language %></language>
|
10
|
+
<language><%= @podcast.language %></language>
|
11
11
|
<generator>gst-kitchen</generator>
|
12
|
-
<copyright><%= podcast.author %>, cc-by-nc-sa</copyright>
|
13
|
-
<itunes:subtitle><%= podcast.subtitle %></itunes:subtitle>
|
14
|
-
<itunes:author><%= podcast.author %></itunes:author>
|
15
|
-
<managingEditor><%= podcast.email %> (<%= podcast.author %>)</managingEditor>
|
16
|
-
<itunes:summary><%= podcast.summary %></itunes:summary>
|
17
|
-
<itunes:explicit><%= podcast.explicit ? "yes" : "no" %></itunes:explicit>
|
18
|
-
<description><%= podcast.summary %></description>
|
12
|
+
<copyright><%= @podcast.author %>, cc-by-nc-sa</copyright>
|
13
|
+
<itunes:subtitle><%= @podcast.subtitle %></itunes:subtitle>
|
14
|
+
<itunes:author><%= @podcast.author %></itunes:author>
|
15
|
+
<managingEditor><%= @podcast.email %> (<%= @podcast.author %>)</managingEditor>
|
16
|
+
<itunes:summary><%= @podcast.summary %></itunes:summary>
|
17
|
+
<itunes:explicit><%= @podcast.explicit ? "yes" : "no" %></itunes:explicit>
|
18
|
+
<description><%= @podcast.summary %></description>
|
19
19
|
<itunes:owner>
|
20
|
-
<itunes:name><%= podcast.author %></itunes:name>
|
21
|
-
<itunes:email><%= podcast.email %></itunes:email>
|
20
|
+
<itunes:name><%= @podcast.author %></itunes:name>
|
21
|
+
<itunes:email><%= @podcast.email %></itunes:email>
|
22
22
|
</itunes:owner>
|
23
23
|
|
24
|
-
<itunes:image href="<%= podcast.cover_url %>"/>
|
24
|
+
<itunes:image href="<%= @podcast.cover_url %>"/>
|
25
25
|
|
26
26
|
<itunes:category text="Technology"/>
|
27
27
|
<itunes:category text="Technology">
|
@@ -31,19 +31,19 @@
|
|
31
31
|
<itunes:category text="Tech News"/>
|
32
32
|
</itunes:category>
|
33
33
|
|
34
|
-
<% podcast.episodes.each do |episode| %>
|
34
|
+
<% @podcast.episodes.each do |episode| %>
|
35
35
|
<item>
|
36
36
|
<title><%= episode.title %></title>
|
37
|
-
<itunes:explicit><%= podcast.explicit ? "yes" : "no" %></itunes:explicit>
|
38
|
-
<itunes:author><%= podcast.author %></itunes:author>
|
37
|
+
<itunes:explicit><%= @podcast.explicit ? "yes" : "no" %></itunes:explicit>
|
38
|
+
<itunes:author><%= @podcast.author %></itunes:author>
|
39
39
|
<itunes:subtitle><%= episode.title %></itunes:subtitle>
|
40
|
-
<enclosure url="<%= podcast.episode_media_url(episode,
|
40
|
+
<enclosure url="<%= @podcast.episode_media_url(episode, @format) %>" length="<%= episode.media[@format.format]["size"] %>" type="<%= @format.mime_type %>"/>
|
41
41
|
<guid isPermaLink="false"><%= episode.handle %></guid>
|
42
42
|
<pubDate><%= episode.rfc_2822_date %></pubDate>
|
43
43
|
<itunes:duration><%= episode.duration %></itunes:duration>
|
44
44
|
<itunes:summary><![CDATA[<%= episode.summary %>]]></itunes:summary>
|
45
|
-
<description><![CDATA[<%= episode.
|
46
|
-
<atom:link rel="http://podlove.org/deep-link" href="<%= podcast.deep_link_url(episode) %>" />
|
45
|
+
<description><![CDATA[<%= render_as_markdown episode.summary %>]]></description>
|
46
|
+
<atom:link rel="http://podlove.org/deep-link" href="<%= @podcast.deep_link_url(episode) %>" />
|
47
47
|
</item>
|
48
48
|
<% end %>
|
49
49
|
</channel>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gst-kitchen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: yajl-ruby
|
@@ -59,6 +59,22 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: redcarpet
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
62
78
|
description: gst-kitchen is a gem to publish podcasts like a nerd with auphonic!
|
63
79
|
email:
|
64
80
|
- sebastian.cohnen@gmx.net
|
@@ -79,11 +95,12 @@ files:
|
|
79
95
|
- lib/gst-kitchen/auphonic/account.rb
|
80
96
|
- lib/gst-kitchen/auphonic/production.rb
|
81
97
|
- lib/gst-kitchen/episode.rb
|
98
|
+
- lib/gst-kitchen/feed.rb
|
82
99
|
- lib/gst-kitchen/media.rb
|
83
100
|
- lib/gst-kitchen/podcast.rb
|
84
101
|
- lib/gst-kitchen/version.rb
|
85
102
|
- templates/episodes.rss.erb
|
86
|
-
homepage:
|
103
|
+
homepage: http://github.com/tisba/gst-kitchen
|
87
104
|
licenses: []
|
88
105
|
post_install_message:
|
89
106
|
rdoc_options: []
|
@@ -97,7 +114,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
114
|
version: '0'
|
98
115
|
segments:
|
99
116
|
- 0
|
100
|
-
hash:
|
117
|
+
hash: 1700055491858221400
|
101
118
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
119
|
none: false
|
103
120
|
requirements:
|
@@ -106,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
123
|
version: '0'
|
107
124
|
segments:
|
108
125
|
- 0
|
109
|
-
hash:
|
126
|
+
hash: 1700055491858221400
|
110
127
|
requirements: []
|
111
128
|
rubyforge_project:
|
112
129
|
rubygems_version: 1.8.24
|