├── .gitignore ├── Gemfile ├── favicon.ico ├── img ├── logo.jpg ├── logo-360x360.png ├── glyphicons-halflings.png └── glyphicons-halflings-white.png ├── episodes.m4a.rss ├── episodes.mp3.rss ├── apple-touch-icon.png ├── _includes ├── post_line.html ├── post_header.html ├── flattr_loader.html ├── disqus_count.html ├── disqus_thread.html ├── post.html └── sidebar.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon-57x57-precomposed.png ├── apple-touch-icon-72x72-precomposed.png ├── apple-touch-icon-114x114-precomposed.png ├── .gitmodules ├── _posts └── dummy ├── episodes └── dummy ├── js ├── custom.js └── bootstrap.min.js ├── _layouts ├── post.html ├── page.html ├── feed.xml └── default.html ├── about.md ├── css ├── custom_styles.css ├── bootstrap-responsive.min.css └── bootstrap.min.css ├── ChangeLog ├── index.html ├── _plugins ├── site.rb ├── static_file.rb ├── flattr_filters.rb └── octopod_filters.rb ├── general_feed.xml ├── _config.yml.sample └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | _config.yml 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'octopod' 4 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/img/logo.jpg -------------------------------------------------------------------------------- /episodes.m4a.rss: -------------------------------------------------------------------------------- 1 | --- 2 | layout: feed 3 | format: m4a 4 | --- 5 | {{ content }} 6 | -------------------------------------------------------------------------------- /episodes.mp3.rss: -------------------------------------------------------------------------------- 1 | --- 2 | layout: feed 3 | format: mp3 4 | --- 5 | {{ content }} 6 | -------------------------------------------------------------------------------- /apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/apple-touch-icon.png -------------------------------------------------------------------------------- /img/logo-360x360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/img/logo-360x360.png -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /_includes/post_line.html: -------------------------------------------------------------------------------- 1 |
2 | {% include post_header.html %} 3 |
4 | -------------------------------------------------------------------------------- /apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /apple-touch-icon-57x57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/apple-touch-icon-57x57-precomposed.png -------------------------------------------------------------------------------- /apple-touch-icon-72x72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/apple-touch-icon-72x72-precomposed.png -------------------------------------------------------------------------------- /apple-touch-icon-114x114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pattex/octopod/HEAD/apple-touch-icon-114x114-precomposed.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "podlove-web-player"] 2 | path = podlove-web-player 3 | url = git@github.com:pattex/podlove-web-player.git 4 | -------------------------------------------------------------------------------- /_posts/dummy: -------------------------------------------------------------------------------- 1 | Remove me if you want. My only purpose was to tell git that this directory 2 | exists, even if it's empty. And I can say: I did my Job very well. 3 | -------------------------------------------------------------------------------- /episodes/dummy: -------------------------------------------------------------------------------- 1 | Remove me if you want. My only purpose was to tell git that this directory 2 | exists, even if it's empty. And I can say: I did my Job very well. 3 | -------------------------------------------------------------------------------- /js/custom.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /* 3 | $("ul.nav li a").each(function() { 4 | if ($(this).attr('href') == location.pathname) { 5 | $(this).parent().addClass("active"); 6 | } 7 | }); 8 | */ 9 | }); 10 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | {% for post in site.posts %} 5 | {% if post.url == page.url %} 6 | {% include post.html %} 7 | {% else %} 8 | {% include post_line.html %} 9 | {% endif %} 10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /_includes/post_header.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: About Octopod 4 | subtitle: podcasting for geeks 5 | navigation: 1 6 | --- 7 | {{ site.description }} 8 | 9 | This Podcast is published by {{ site.author }}. 10 | Drop me a mail ({{ site.email }}) if you 11 | want. 12 | -------------------------------------------------------------------------------- /css/custom_styles.css: -------------------------------------------------------------------------------- 1 | div.container a.brand { 2 | font-size: 30px; 3 | padding: 10px 20px 20px; 4 | } 5 | div.audio-meta { vertical-align: baseline; } 6 | div.audio-player { padding: 5px 0 0 0; } 7 | div #footer small { color: #999; } 8 | div.navbar-inner { -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; } 9 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | == 0.2.1 [2013-03-08] 2 | 3 | * Fixed a podlove-web-player integration bug, that thought of I already had 4 | 5 | == 0.2.0 [2013-03-08] 6 | 7 | * Updated podlove-web-player to v2.0.5 8 | * Fixed some podlove-web-player integration failures 9 | 10 | == 0.1.0 [2013-02-27] 11 | 12 | * Got a version number. ;-) 13 | 14 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | {% if page.title %} 6 | 12 | {% endif %} 13 | 14 | {{ content }} 15 |
16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Episodes 3 | layout: default 4 | navigation: 0 5 | --- 6 | {% assign post = site.posts.first %} 7 | {% include post.html %} 8 | 9 | {% assign current_post = site.posts.first %} 10 | {% for post in site.posts %} 11 | {% unless post.url == current_post.url %} 12 | {% include post_line.html %} 13 | {% endunless %} 14 | {% endfor %} 15 | -------------------------------------------------------------------------------- /_plugins/site.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | class Site 3 | 4 | alias_method :_octopod_original_process, :process 5 | 6 | def process 7 | feeds = Dir["episodes.{???,????}.rss"] 8 | config.update( 9 | 'episode_feed_formats' => feeds.map { |f| f.match(/episodes\.(\w{3,4})\.rss/)[1] } 10 | ) 11 | 12 | _octopod_original_process 13 | end 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /_includes/flattr_loader.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /_includes/disqus_count.html: -------------------------------------------------------------------------------- 1 | {% if site.disqus_shortname %} 2 | 3 | 12 | 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /_includes/disqus_thread.html: -------------------------------------------------------------------------------- 1 | {% if site.disqus_shortname %} 2 |
3 | 11 | 12 | comments powered by Disqus 13 | {% endif %} 14 | -------------------------------------------------------------------------------- /_includes/post.html: -------------------------------------------------------------------------------- 1 | {% if post %} 2 | {% elsif page %} 3 | {% assign post = page %} 4 | {% endif %} 5 |
6 | {% include post_header.html %} 7 | {{ post.content }} 8 | {% if post.audio %} 9 | {% for file in post.audio %} 10 | Download .{{ file.first }} ({{ file.last | file_size:'../' | string_of_size }}) 11 | {% endfor %} 12 | {% endif %} 13 |
14 | {{ site | flattr_button:post }} 15 | Tweet 16 | {% unless page.url == '/index.html' %}{% include disqus_thread.html %}{% endunless %} 17 |
18 |
19 | -------------------------------------------------------------------------------- /_includes/sidebar.html: -------------------------------------------------------------------------------- 1 | 7 | {{ site.title }} logo 8 |
9 |

10 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 11 |

12 |
13 | {{ site | flattr_button }} 14 | {% if site.twitter_nick %} 15 | Follow @{{ site.twitter_nick }} 16 | {% endif %} 17 | -------------------------------------------------------------------------------- /general_feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: nil 3 | --- 4 | 5 | 6 | 7 | {{ site.title }} 8 | 9 | 10 | {{ site.time | date_to_xmlschema }} 11 | {{ site.url }}/ 12 | 13 | {{ site.author }} 14 | {{ site.email }} 15 | 16 | 17 | {% for post in site.posts %} 18 | 19 | {{ post.title }} 20 | 21 | {{ post.date | date_to_xmlschema }} 22 | {{ site.url }}{{ post.id }} 23 | {{ post.content | xml_escape }} 24 | {% for file in post.audio %} 25 | 26 | {% endfor %} 27 | {{ site | flattr_atom:post }} 28 | 29 | {% endfor %} 30 | 31 | 32 | -------------------------------------------------------------------------------- /_plugins/static_file.rb: -------------------------------------------------------------------------------- 1 | module Jekyll 2 | class StaticFile 3 | 4 | def octopod_exclude 5 | src = path.sub("#{@base}/", '') 6 | nested_files = %w[img/bigplay.psd podlove-web-player.php readme.txt 7 | screenshot-1.png screenshot-2.png settings.php standalone.html 8 | libs/mediaelement/README.md] 9 | exclude_dirs = %w[ 10 | podlove-web-player/podlove-web-player/samples 11 | podlove-web-player/podlove-web-player/libs/mediaelement/demo 12 | podlove-web-player/podlove-web-player/libs/mediaelement/media 13 | ] 14 | 15 | excludes = %w[ChangeLog Gemfile Gemfile.lock README.md octopod podlove-web-player/readme.md] 16 | excludes.concat(nested_files.map { |f| File.join('podlove-web-player/podlove-web-player', f) }) 17 | 18 | return true if excludes.include?(src) || exclude_dirs.include?(File.dirname(src)) 19 | end 20 | 21 | alias_method :_octopod_original_write, :write 22 | 23 | def write(dest) 24 | return true if octopod_exclude 25 | 26 | _octopod_original_write(dest) 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /_config.yml.sample: -------------------------------------------------------------------------------- 1 | # You have to configure this ################################################### 2 | title: Octopod 3 | # You should configure this #################################################### 4 | url: 5 | subtitle: podcast delivery for geeks 6 | description: My super duper cool podcast. 7 | author: Uncle Octopod 8 | email: octopod@example.com 9 | keywords: [octopod, podcast, magic] 10 | itunes_categories: [Technology] 11 | # additional_feeds: 12 | # itunes: http://itunes.apple.com/de/podcast/podcast_name/id42424242 13 | # torrent_m4a: http://bitlove.org/example_user/example_podcast_m4a/feed 14 | # torrent_mp3: http://bitlove.org/example_user/example_podcast_mp3/feed 15 | ## podlove-web-palyer ########################################################## 16 | # poster: /img/logo-360x360.png 17 | # alwaysShowHours: true 18 | # startVolume: 0.8 19 | # width: auto 20 | # summaryVisible: false 21 | # timecontrolsVisible: false 22 | # chaptersVisible: true 23 | # sharebuttonsVisible: false 24 | ## Rsync Deploy config ######################################################### 25 | ### Be sure your public key is listed in your server's ~/.ssh/authorized_keys 26 | ### file. 27 | ssh_user: user@host 28 | ssh_port: 22 29 | document_root: /path/to/your/htdocs/ 30 | rsync_delete: true 31 | # You can configure this ####################################################### 32 | twitter_nick: 33 | language: en 34 | explicit: 'no' # 'yes'/'no'/clean 35 | # license: CC BY-NC-SA 3.0 36 | ## Flattr ###################################################################### 37 | flattr_uid: # Flattr will not be used unless this is set 38 | flattr_button: compact # compact | default 39 | flattr_mode: auto # auto | manual(default) 40 | flattr_popout: 0 # 1 | 0 (show popout when hovering mouse over button) 41 | flattr_language: en_GB # available languages - https://api.flattr.com/rest/v2/languages.txt 42 | flattr_category: audio # available categories - https://api.flattr.com/rest/v2/categories.txt 43 | ## Disqus comments ############################################################# 44 | disqus_shortname: 45 | disqus_developer: 0 # 1 / 0 46 | 47 | -------------------------------------------------------------------------------- /_layouts/feed.xml: -------------------------------------------------------------------------------- 1 | --- 2 | layout: nil 3 | --- 4 | 5 | 6 | 7 | {{ site.title }} 8 | {{ site.url }} 9 | 10 | {{ site | episode_feeds_rss:page.format }} 11 | {{ site.description | markdownify | strip_html }} 12 | {{ site.time | time_to_rssschema }} 13 | {{ site.language }} 14 | 15 | {{ site.description | markdownify | strip_html }} 16 | {{ site.author }} 17 | {{ site.explicit }} 18 | 19 | 20 | {{ site.author }} 21 | {{ site.email }} 22 | 23 | {{site.email}} ({{site.author}}) 24 | {{ site.license }} 25 | {{ site.subtitle }} 26 | {{ site.keywords | join:', ' }} 27 | 28 | {{ site.title }} 29 | {{ site.url }}/img/logo.jpg 30 | {{ site.url }} 31 | 32 | {% for category in site.itunes_categories %} 33 | 34 | {% endfor %} 35 | 36 | {% for post in site.posts %}{% if post.audio[page.format] %} 37 | 38 | {{ post.title }} 39 | {{ site.url }}{{ post.url }} 40 | {{ site.url }}{{ post.url }}#disqus_thread 41 | {{ post.date | time_to_rssschema }} 42 | {% for category in post.categories %} 43 | 44 | {% endfor %} 45 | {{ site.url }}{{ post.url }} 46 | 47 | 48 | 49 | {{ post.tags }} 50 | {{ post.subtitle }} 51 | {{ post.summary }} 52 | {{ post.author | otherwise:site.author }} 53 | {{ post.explicit | otherwise:site.explicit }} 54 | {{ post.duration }} 55 | {{ site | flattr_rss:post }} 56 | {% if post.chapters %} 57 | 58 | {% for chapter in post.chapters %} 59 | 60 | {% endfor %} 61 | 62 | {% endif %} 63 | 64 | {% endif %}{% endfor %} 65 | 66 | 67 | -------------------------------------------------------------------------------- /_plugins/flattr_filters.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | module Jekyll 4 | module FlattrFilters 5 | 6 | # Generates the query string part for the flattr load.js from the 7 | # configurations in _config.yml 8 | # 9 | # {{ site | flattr_loader_options }} 10 | def flattr_loader_options(site) 11 | return if site['flattr_uid'].nil? 12 | keys = %w[mode https popout uid button language category] 13 | options = flattr_options(site, nil, keys).delete_if { |_, v| v.to_s.empty? } 14 | 15 | options.map { |k, v| "#{k}=#{ERB::Util.url_encode(v)}" }.join('&') 16 | end 17 | 18 | # Returns a flattr button 19 | # 20 | # {{ site | flattr_button:page }} 21 | def flattr_button(site, page = nil) 22 | return if site['flattr_uid'].nil? 23 | 24 | keys = %w[url title description uid popout button category language tags] 25 | options = flattr_options(site, page, keys) 26 | 27 | button = '" 33 | end 34 | 35 | # Returns a RSS payment link. 36 | # 37 | # {{ site | flattr_rss:post }} 38 | def flattr_rss(site, page = nil) 39 | return if site['flattr_uid'].nil? 40 | link = '} 42 | end 43 | 44 | # Returns a ATOM payment link. 45 | # 46 | # {{ site | flattr_atom:post }} 47 | def flattr_atom(site, page = nil) 48 | return if site['flattr_uid'].nil? 49 | link = '} 51 | end 52 | 53 | # Removes all leading "flattr_" from the keys of the given hash. 54 | # 55 | # flattrize({ 'octopod' => 'awesome', 'flattr_uid' => 'pattex' }) 56 | # => { "octopod" => "awesome", "uid" => "pattex" } 57 | def flattrize(hsh) 58 | config = {} 59 | hsh.each { |k, v| 60 | if new_key = k.to_s.match(/\Aflattr_(.*)\z/) 61 | config[new_key[1]] = v 62 | else 63 | config[k] = v 64 | end 65 | } 66 | 67 | config 68 | end 69 | 70 | def flattr_options(site, page, keys) 71 | page = {} if page.nil? 72 | site = flattrize(site) 73 | page = flattrize(page) 74 | options = {} 75 | 76 | keys.each { |k| 77 | case k 78 | when 'https' 79 | options[k] = 1 80 | when 'url' 81 | options[k] = "#{site['url']}#{page['url']}" 82 | when 'description' 83 | options[k] = page['content'] || site['description'] || site['title'] 84 | when 'category' 85 | options[k] = page['category'] || site['category'] || 'audio' 86 | when 'language' 87 | options[k] = page['language'] || site['language'] || 'en_GB' 88 | when 'tags' 89 | options[k] = page['tags'].join(', ') if page['tags'] 90 | else 91 | options[k] = page[k] || site[k] 92 | end 93 | } 94 | 95 | options 96 | end 97 | 98 | def flattr_feed_options(site, page) 99 | keys = %w[url uid] 100 | options = flattr_options(site, page, keys).map { |k, v| 101 | "#{k == 'uid' ? 'user_id' : k}=#{ERB::Util.url_encode(v)}" 102 | }.join('&') 103 | end 104 | 105 | end 106 | end 107 | 108 | Liquid::Template.register_filter(Jekyll::FlattrFilters) 109 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ site.title }} - {{ page.title | otherwise:site.subtitle }} 8 | 9 | 10 | {% if site.description %}{% endif %} 11 | {% if site.author %}{% endif %} 12 | 13 | 14 | {{ site | episode_feeds_html }} 15 | 16 | 17 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% if site.flattr_uid %}{% include flattr_loader.html %}{% endif %} 31 | {% if site.disqus_shortname %} 32 | 35 | {% endif %} 36 | 37 | 38 | 39 | 61 |
62 |
63 |
64 |
65 | {{ content }} 66 |
67 |
68 | 73 |
74 | 77 |
78 | 79 | 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Octopod - podcast delivery for geeks 2 | Version 0.2.1 3 | Flattr this 4 | 5 | Octopod is a set of [Jekyll](http://jekyllrb.com/) templates, helpers and extensions to deliver your podcasts the cool text file lover's way. 6 | If you want to take a look: I made [this demo](http://pattex.github.com/octopod). 7 | 8 | Octopod logo 9 | 10 | If you are not afraid of the command line of your computer and text files are the stuff to heat up your geeky little heart, Octopod may be worth a trial to publish your podcasts. 11 | 12 | The underlying assumptions of Octopod are that static content should be delivered statically and text files are the perfect way to handle podcast metadata. So Octopod makes it easy to generate and deploy a website and feeds for your podcast out of one textfile and at least one audio file per episode. 13 | 14 | Octopod brings innately: 15 | * iTunes ready episode feeds for different file formats 16 | * a ready to use [Twitter Bootstrap](http://twitter.github.com/bootstrap/) based layout 17 | * [Flattr](https://flattr.com/) support on the website and in the episode feed 18 | * comments via [Disqus](http://disqus.com/) 19 | * [Podlove Web Player](http://podlove.org/podlove-web-player/) 20 | * [Podlove Alternate Feeds](http://podlove.org/alternate-feeds/) 21 | * [Podlove Simple Chapters](http://podlove.org/simple-chapters/) 22 | 23 | ***Attention: This project is absolutely in a non 1.0 status. This means that there is no guarantee for not breaking this or that behavior with the next update. Hopefully you are brave enough to play with this cute little toy anyway. [Bug reports](https://github.com/pattex/octopod/issues) and even more push requests are highly welcome.*** 24 | 25 | ## Requirements, installation and setup 26 | Since Jekyll and Octopod are written in the [Ruby programming language](http://www.ruby-lang.org/) you need a running Ruby interpreter – which sadly is sometimes easier said than done – and the [bundler](http://gembundler.com/) gem installed. 27 | And because Octopod lives in a git repository, you may also want to have the [git version control system](http://git-scm.com/) installed. 28 | 29 | The first step to your new and shiny podcast publishing system is to get a copy of it. And to get one, the best way should be to clone the repo. 30 | So open your Terminal and type the following command: 31 | 32 | git clone --recursive https://github.com/pattex/octopod.git 33 | 34 | Now you have to install all the required software to run Octopod. 35 | 36 | cd octopod 37 | bundle install 38 | 39 | To set up your Octopod, just rename the sample config file and edit it as you need. 40 | 41 | mv _config.yml.sample _config.yml 42 | _config.yml 43 | 44 | *You can find a documentation of all the non Octopod specific settings on the [Configuration page of the Jekyll Wiki](https://github.com/mojombo/jekyll/wiki/Configuration).* 45 | 46 | ## Usage 47 | First of all, your new podcast episode needs audio data. Octopod assumes that your ready to use audio files stay within the `episodes` directory in in your projects root. 48 | 49 | cp ~/my_superduper_audio_files/ocp001.m4a episodes 50 | 51 | In addition, Octopod assumes that the different audio files of each of your episode are the same content in various formats which are the same duration and everything. 52 | If they are not, you may confuse your listeners. At the least when you are using the chapters feature. 53 | 54 | Next up your episode needs some metadata. A title, some kind of a description, maybe chapters and so on. Octopod keeps all these metadata in one single textfile (Protip: these dear little tots feel very lucky when they might live in a version control system! You already got one for free when you `git clone`d the Octopod repo). 55 | Octopod kindly helps you generating these file with the `octopod episode` command (You will find some more inforamtion on the `octopod` command line tool in [the Octopod wiki](https://github.com/pattex/octopod/wiki/The-octopod-command-line-tool)). 56 | 57 | The following command 58 | 59 | octopod episode --title "Why I <3 Octopod" 60 | 61 | will generate you a template called **YYYY-MM-DD-why-i-_3-octopod.md** (YYYY-MM-DD represents the current date) in your `_posts` subdirectory. When you open it in your text editor you'll see something like that: 62 | 63 | --- 64 | title: Why I <3 Octopod 65 | layout: post 66 | author: Uncle Octopod 67 | explicit: 'no' 68 | audio: 69 | m4a: name.m4a 70 | mp3: name.mp3 71 | opus: name.opus 72 | --- 73 | Insert eloquent and worth reading text here. 74 | 75 | {{ page | web_player:site }} 76 | 77 | ## Shownotes 78 | * Note 79 | 80 | The part between the "---" and the "---" is [the YAML front matter](https://github.com/pattex/octopod/wiki/The-post-template). This is where all the metadata is stored. Below the YAML frontmatter is the area (body) where you can write down your posts content (like the show notes and stuff). This strange looking `{{ page | web_player:site }}` thingy is the [Liquid filter](https://github.com/pattex/octopod/wiki/Liquid-filters) which represents the web player later. 81 | 82 | When all this work is done you are ready to take a first look. 83 | The following command generates your site and start a local webserver for a preview. You kann check your new pocasting website by opening [http://localhost:4000](http://localhost:4000). 84 | 85 | octopod --url "http://localhost:4000" --server 86 | 87 | If everything is alright you can finally generate your "real" Site: 88 | 89 | octopod 90 | 91 | And deploy it to your server: 92 | 93 | octopod deploy 94 | 95 | *Attention*: The Rsync settings in your `_config.yml` are mandatory for this last step. But it is no problem to upload the generated website from the `_site` subdirectory via FTP or something. 96 | 97 | ## Contributing 98 | 99 | At the moment Octopod is extremely made to fit my personal needs and preferences. But it's it is a high priority goal to become Octopod more generic. This and of course bug fixes would make the sweet little opctopd smile the most. 100 | 101 | But feel free to fork and push request and code and everything. 102 | 103 | 1. Fork it 104 | 2. Create your feature branch (`git checkout -b my-new-feature`) 105 | 3. Commit your changes (`git commit -am 'Add some feature'`) 106 | 4. Push to the branch (`git push origin my-new-feature`) 107 | 5. Create new Pull Request 108 | 109 | ## Credits 110 | The beautiful Octopod Logo was designed and created by [Thekla "TeMeL" Löhr](http://www.temel-art.de/). Please support her with a little flattr. 111 | Flattr this 112 | 113 | Some of this code is not written by me. 114 | I shamelessly took some lines from other projects. And of cause Octopod is based on a list of frameworks, tools and plugins. 115 | 116 | Gratitude be to: 117 | * [Jekyll](http://jekyllrb.com/) 118 | * [Twitter Bootstrap](http://twitter.github.com/bootstrap/) 119 | * [The whole Podlove project](http://podlove.org/) 120 | * [Octopress](http://octopress.org/) 121 | 122 | ## License 123 | 124 | ### The MIT License 125 | 126 | Copyright (c) 2012 Arne Eilermann 127 | 128 | Permission is hereby granted, free of charge, to any person obtaining a copy 129 | of this software and associated documentation files (the "Software"), to deal 130 | in the Software without restriction, including without limitation the rights 131 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 132 | copies of the Software, and to permit persons to whom the Software is 133 | furnished to do so, subject to the following conditions: 134 | 135 | The above copyright notice and this permission notice shall be included in 136 | all copies or substantial portions of the Software. 137 | 138 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 139 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 140 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 141 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 142 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 143 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 144 | THE SOFTWARE. 145 | 146 | ### Artwork 147 | 148 | Octopod Logo by [Thekla "TeMeL" Löhr](http://www.temel-art.de/) is licensed 149 | under a [Creative Commons Attribution 3.0 Germany License](http://creativecommons.org/licenses/by/3.0/de/). 150 | -------------------------------------------------------------------------------- /css/bootstrap-responsive.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.0.4 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}@media(max-width:767px){.visible-phone{display:inherit!important}.hidden-phone{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(min-width:768px) and (max-width:979px){.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:18px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-group>label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.modal{position:absolute;top:10px;right:10px;left:10px;width:auto;margin:0}.modal.fade.in{top:auto}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:auto;margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:20px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.762430939%;*margin-left:2.709239449638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:99.999999993%;*width:99.9468085036383%}.row-fluid .span11{width:91.436464082%;*width:91.38327259263829%}.row-fluid .span10{width:82.87292817100001%;*width:82.8197366816383%}.row-fluid .span9{width:74.30939226%;*width:74.25620077063829%}.row-fluid .span8{width:65.74585634900001%;*width:65.6926648596383%}.row-fluid .span7{width:57.182320438000005%;*width:57.129128948638304%}.row-fluid .span6{width:48.618784527%;*width:48.5655930376383%}.row-fluid .span5{width:40.055248616%;*width:40.0020571266383%}.row-fluid .span4{width:31.491712705%;*width:31.4385212156383%}.row-fluid .span3{width:22.928176794%;*width:22.874985304638297%}.row-fluid .span2{width:14.364640883%;*width:14.311449393638298%}.row-fluid .span1{width:5.801104972%;*width:5.747913482638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:714px}input.span11,textarea.span11,.uneditable-input.span11{width:652px}input.span10,textarea.span10,.uneditable-input.span10{width:590px}input.span9,textarea.span9,.uneditable-input.span9{width:528px}input.span8,textarea.span8,.uneditable-input.span8{width:466px}input.span7,textarea.span7,.uneditable-input.span7{width:404px}input.span6,textarea.span6,.uneditable-input.span6{width:342px}input.span5,textarea.span5,.uneditable-input.span5{width:280px}input.span4,textarea.span4,.uneditable-input.span4{width:218px}input.span3,textarea.span3,.uneditable-input.span3{width:156px}input.span2,textarea.span2,.uneditable-input.span2{width:94px}input.span1,textarea.span1,.uneditable-input.span1{width:32px}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;content:""}.row:after{clear:both}[class*="span"]{float:left;margin-left:30px}.container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:28px;margin-left:2.564102564%;*margin-left:2.510911074638298%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145300001%;*width:91.3997999636383%}.row-fluid .span10{width:82.905982906%;*width:82.8527914166383%}.row-fluid .span9{width:74.358974359%;*width:74.30578286963829%}.row-fluid .span8{width:65.81196581200001%;*width:65.7587743226383%}.row-fluid .span7{width:57.264957265%;*width:57.2117657756383%}.row-fluid .span6{width:48.717948718%;*width:48.6647572286383%}.row-fluid .span5{width:40.170940171000005%;*width:40.117748681638304%}.row-fluid .span4{width:31.623931624%;*width:31.5707401346383%}.row-fluid .span3{width:23.076923077%;*width:23.0237315876383%}.row-fluid .span2{width:14.529914530000001%;*width:14.4767230406383%}.row-fluid .span1{width:5.982905983%;*width:5.929714493638298%}input,textarea,.uneditable-input{margin-left:0}input.span12,textarea.span12,.uneditable-input.span12{width:1160px}input.span11,textarea.span11,.uneditable-input.span11{width:1060px}input.span10,textarea.span10,.uneditable-input.span10{width:960px}input.span9,textarea.span9,.uneditable-input.span9{width:860px}input.span8,textarea.span8,.uneditable-input.span8{width:760px}input.span7,textarea.span7,.uneditable-input.span7{width:660px}input.span6,textarea.span6,.uneditable-input.span6{width:560px}input.span5,textarea.span5,.uneditable-input.span5{width:460px}input.span4,textarea.span4,.uneditable-input.span4{width:360px}input.span3,textarea.span3,.uneditable-input.span3{width:260px}input.span2,textarea.span2,.uneditable-input.span2{width:160px}input.span1,textarea.span1,.uneditable-input.span1{width:60px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:18px}.navbar-fixed-bottom{margin-top:18px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 9px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#999;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:6px 15px;font-weight:bold;color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#222}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:block;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:9px 15px;margin:9px 0;border-top:1px solid #222;border-bottom:1px solid #222;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} 10 | -------------------------------------------------------------------------------- /_plugins/octopod_filters.rb: -------------------------------------------------------------------------------- 1 | #require 'erb' 2 | require 'uri' 3 | require 'digest/sha1' 4 | 5 | module Jekyll 6 | module OctopodFilters 7 | JSON_ENTITIES = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C', "'" => '\u0027' } 8 | 9 | # Escapes some text for CDATA 10 | def cdata_escape(input) 11 | input.gsub(//, ']]>') 12 | end 13 | 14 | # Escapes HTML entities in JSON strings. 15 | # More or less a copy of the equivalent method in Active Support. 16 | # https://github.com/rails/rails/tree/master/activesupport 17 | def j(str) 18 | str.to_s.gsub(/[&"><']/) { |e| JSON_ENTITIES[e] } 19 | end 20 | 21 | # Replaces relative urls with full urls 22 | # 23 | # {{ "about.html" | expand_urls }} => "/about.html" 24 | # {{ "about.html" | expand_urls:site.url }} => "http://example.com/about.html" 25 | def expand_urls(input, url='') 26 | url ||= '/' 27 | input.gsub /(\s+(href|src)\s*=\s*["|']{1})(\/[^\"'>]*)/ do 28 | $1+url+$3 29 | end 30 | end 31 | 32 | # Formats a Time to be RSS compatible like "Wed, 15 Jun 2005 19:00:00 GMT" 33 | # 34 | # {{ site.time | time_to_rssschema }} 35 | def time_to_rssschema(time) 36 | time.strftime("%a, %d %b %Y %H:%M:%S %z") 37 | end 38 | 39 | # Returns the first argument if it's not nil or empty otherwise it returns 40 | # the second one. 41 | # 42 | # {{ post.author | otherwise:site.author }} 43 | def otherwise(first, second) 44 | first = first.to_s 45 | first.empty? ? second : first 46 | end 47 | 48 | # Returns the value of a given hash. Is no key as second parameter given, it 49 | # trys first "mp3", than "m4a" and than it will return a more or less random 50 | # value. 51 | # 52 | # {{ post.audio | audio:"m4a" }} => "my-episode.m4a" 53 | def audio(hsh, key = nil) 54 | if key.nil? 55 | hsh['mp3'] ? hsh['mp3'] : hsh['m4a'] ? hsh['m4a'] : hsh.values.first 56 | else 57 | hsh[key] 58 | end 59 | end 60 | 61 | # Returns the MIME-Type of a given file format. 62 | # 63 | # {{ "m4a" | mime_type }} => "audio/mp4a-latm" 64 | def mime_type(format) 65 | types = { 66 | 'mp3' => 'mpeg', 67 | 'm4a' => 'mp4a-latm', 68 | 'ogg' => 'ogg; codecs=vorbis', 69 | 'opus' => 'ogg; codecs=opus' 70 | } 71 | 72 | "audio/#{types[format]}" 73 | end 74 | 75 | # Returns the size of a given file in bytes. If there is just a filename 76 | # without a path, this method assumes that the file is an episode audio file 77 | # which lives in /episodes. 78 | # 79 | # {{ "example.m4a" | file_size }} => 4242 80 | def file_size(path, rel = nil) 81 | return 0 if path.nil? 82 | path = path =~ /\// ? path : File.join('episodes', path) 83 | path = rel + path if rel 84 | File.size(path) 85 | end 86 | 87 | # Returns a slug based on the id of a given page. 88 | # 89 | # {{ page | slug }} => '2012_10_02_octopod' 90 | def slug(page) 91 | page['id'][1..-1].gsub('/', '_') 92 | end 93 | 94 | # Splits a chapter, like it is written to the post YAML front matter into 95 | # the components 'start' which refers to a single point in time relative to 96 | # the beginning of the media file nad 'title' which defines the text to be 97 | # the title of the chapter. 98 | # 99 | # {{ '00:00:00.000 Welcome to Octopod!' | split_chapter }} 100 | # => { 'start' => '00:00:00.000', 'title' => 'Welcome to Octopod!' } 101 | # 102 | # {{ '00:00:00.000 Welcome to Octopod!' | split_chapter:'title' }} 103 | # => 'Welcome to Octopod!' 104 | # 105 | # {{ '00:00:00.000 Welcome to Octopod!' | split_chapter:'start' }} 106 | # => '00:00:00.000' 107 | def split_chapter(chapter_str, attribute = nil) 108 | attributes = chapter_str.split(/ /, 2) 109 | return nil unless attributes.first.match(/\A(\d|:|\.)+\z/) 110 | 111 | if attribute.nil? 112 | { 'start' => attributes.first, 'title' => attributes.last } 113 | else 114 | attribute == 'start' ? attributes.first : attributes.last 115 | end 116 | end 117 | 118 | # Returns an