ipodcastly 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.md +21 -0
- data/bin/ipodcastly +29 -0
- data/lib/ipodcastly.rb +12 -0
- data/lib/ipodcastly/client/episode.rb +52 -0
- data/lib/ipodcastly/client/podcast.rb +30 -0
- data/lib/ipodcastly/server/episode.rb +35 -0
- data/lib/ipodcastly/server/podcast.rb +41 -0
- data/lib/ipodcastly/sync.rb +76 -0
- metadata +113 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Denis Barushev
|
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,21 @@
|
|
1
|
+
# Ipodcastly
|
2
|
+
|
3
|
+
Ipodcastly is a console client for syncing local iTunes library with
|
4
|
+
[Podcastly](http://podcastly.com). The gem was built as a simple example to
|
5
|
+
demonstrate how [Podcastly](http://podcastly.com)'s API works.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
The installation is pretty standard:
|
10
|
+
|
11
|
+
sudo gem install ipodcastly
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Just run it in the Terminal:
|
16
|
+
|
17
|
+
ipodcastly
|
18
|
+
|
19
|
+
## Copyright
|
20
|
+
|
21
|
+
Copyright (c) 2010 Denis Barushev. See LICENSE for details.
|
data/bin/ipodcastly
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
3
|
+
|
4
|
+
require 'ipodcastly'
|
5
|
+
require 'choice'
|
6
|
+
|
7
|
+
Choice.options do
|
8
|
+
banner "Usage: #{File.basename(__FILE__)}"
|
9
|
+
separator ''
|
10
|
+
separator 'Common options: '
|
11
|
+
|
12
|
+
option :help do
|
13
|
+
long '--help'
|
14
|
+
desc 'Show this message'
|
15
|
+
end
|
16
|
+
|
17
|
+
option :version do
|
18
|
+
short '-v'
|
19
|
+
long '--version'
|
20
|
+
desc 'Show version'
|
21
|
+
action do
|
22
|
+
puts "#{File.basename(__FILE__)} #{File.exist?('VERSION') ? File.read('VERSION') : ''}"
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
options = Choice.choices
|
29
|
+
Ipodcastly::Sync.start(options)
|
data/lib/ipodcastly.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Ipodcastly
|
2
|
+
module Client
|
3
|
+
class Episode
|
4
|
+
attr_reader :reference
|
5
|
+
|
6
|
+
def initialize(reference)
|
7
|
+
@reference = reference
|
8
|
+
end
|
9
|
+
|
10
|
+
def title
|
11
|
+
@title ||= @reference.name.get
|
12
|
+
end
|
13
|
+
|
14
|
+
def duration
|
15
|
+
@duration ||= @reference.duration.get
|
16
|
+
end
|
17
|
+
|
18
|
+
def position
|
19
|
+
@position ||= @reference.played_count.get > 0 ? duration : @reference.bookmark.get
|
20
|
+
end
|
21
|
+
|
22
|
+
def position=(time)
|
23
|
+
@position = time
|
24
|
+
@reference.bookmark.set(time)
|
25
|
+
end
|
26
|
+
|
27
|
+
def mark_as_listened
|
28
|
+
@reference.played_count.set(1)
|
29
|
+
end
|
30
|
+
|
31
|
+
def enabled?
|
32
|
+
@reference.enabled.get
|
33
|
+
end
|
34
|
+
|
35
|
+
def audio?
|
36
|
+
@reference.video_kind.get == :none
|
37
|
+
end
|
38
|
+
|
39
|
+
def never_played?
|
40
|
+
position.zero? && @reference.played_count.get.zero?
|
41
|
+
end
|
42
|
+
|
43
|
+
def updateable?
|
44
|
+
enabled? && never_played?
|
45
|
+
end
|
46
|
+
|
47
|
+
def listened?
|
48
|
+
enabled? && !never_played?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ipodcastly
|
2
|
+
module Client
|
3
|
+
class Podcast
|
4
|
+
attr_accessor :title
|
5
|
+
|
6
|
+
@@itunes = Appscript.app('iTunes')
|
7
|
+
@@podcasts = @@itunes.playlists.get.detect { |p| p.special_kind.get == :Podcasts }
|
8
|
+
|
9
|
+
def initialize(title)
|
10
|
+
@title = title
|
11
|
+
end
|
12
|
+
|
13
|
+
def episodes
|
14
|
+
@@podcasts.tracks.get.select { |t| t.album.get == title }.map { |t| Episode.new(t) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.all
|
18
|
+
@@podcasts.tracks.get.map { |t| t.album.get }.uniq.map { |title| new(title) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.find_by_title(title)
|
22
|
+
all.detect { |p| p.title == title }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.subscribe(url)
|
26
|
+
@@itunes.subscribe(url)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Ipodcastly
|
2
|
+
module Server
|
3
|
+
class Episode
|
4
|
+
include HTTParty
|
5
|
+
base_uri 'http://podcastly.com/api/v1'
|
6
|
+
default_params :api_key => Ipodcastly::Sync.api_key
|
7
|
+
|
8
|
+
attr_reader :id, :podcast_id, :title, :position, :duration
|
9
|
+
|
10
|
+
def initialize(id, podcast_id, title, position, duration = 0)
|
11
|
+
@id = id
|
12
|
+
@podcast_id = podcast_id
|
13
|
+
@title = title
|
14
|
+
@position = position
|
15
|
+
@duration = duration
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_param
|
19
|
+
{
|
20
|
+
:podcast_id => podcast_id,
|
21
|
+
:episode => {
|
22
|
+
:title => title,
|
23
|
+
:position => position.to_i,
|
24
|
+
:duration => duration.to_i
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def save
|
30
|
+
response = self.class.post("/episodes.json", :body => to_param)
|
31
|
+
response.code == 200
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Ipodcastly
|
2
|
+
module Server
|
3
|
+
class Podcast
|
4
|
+
include HTTParty
|
5
|
+
base_uri 'http://podcastly.com/api/v1'
|
6
|
+
default_params :api_key => Ipodcastly::Sync.api_key
|
7
|
+
|
8
|
+
attr_reader :id, :title, :url
|
9
|
+
|
10
|
+
def initialize(id, title, url)
|
11
|
+
@id = id
|
12
|
+
@title = title
|
13
|
+
@url = url
|
14
|
+
end
|
15
|
+
|
16
|
+
def episodes
|
17
|
+
response = self.class.get("/episodes.json?podcast_id=#{id}")
|
18
|
+
|
19
|
+
if response.code == 200
|
20
|
+
response.map { |e| Episode.new(e['id'], id, e['title'], e['position'], e['duration']) }
|
21
|
+
else
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.all
|
27
|
+
response = get('/podcasts.json')
|
28
|
+
|
29
|
+
if response.code == 200
|
30
|
+
response.map { |p| new(p['id'], p['title'], p['url']) }
|
31
|
+
else
|
32
|
+
[]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_by_title(title)
|
37
|
+
all.detect { |p| p.title == title }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Ipodcastly
|
2
|
+
class Sync
|
3
|
+
def self.api_key
|
4
|
+
begin
|
5
|
+
@api_key ||= YAML::load(File.open(File.join(ENV['HOME'], '.ipodcastly')))['api_key']
|
6
|
+
rescue
|
7
|
+
puts "API key is missing."
|
8
|
+
exit
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# User listened an episode in Podcastly, he didn't listen it in the
|
13
|
+
# iTunes, the episode already downloaded in the iTunes.
|
14
|
+
def self.sync_itunes
|
15
|
+
Server::Podcast.all.each do |server_podcast|
|
16
|
+
until client_podcast = Client::Podcast.find_by_title(server_podcast.title)
|
17
|
+
Client::Podcast.subscribe(server_podcast.url)
|
18
|
+
puts "Subscribing to #{server_podcast.url}"
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "\n+ Podcast \"#{server_podcast.title}\""
|
22
|
+
|
23
|
+
# Only episodes the user listened
|
24
|
+
server_podcast.episodes.each do |server_episode|
|
25
|
+
next unless client_episode = client_podcast.episodes.detect { |client_episode| client_episode.title == server_episode.title }
|
26
|
+
|
27
|
+
if client_episode.updateable?
|
28
|
+
if !server_episode.duration.zero? && (server_episode.position == server_episode.duration)
|
29
|
+
client_episode.mark_as_listened
|
30
|
+
else
|
31
|
+
client_episode.position = server_episode.position / 1000.0
|
32
|
+
end
|
33
|
+
|
34
|
+
puts " + Episode \"#{server_episode.title}\" #{server_episode.duration.zero? ? "#{(server_episode.position / 1000)} sec." : "#{(server_episode.position * 100/server_episode.duration).to_i}%"}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end # self.sync_itunes
|
39
|
+
|
40
|
+
def self.sync_podcastly
|
41
|
+
Client::Podcast.all.each do |client_podcast|
|
42
|
+
unless server_podcast = Server::Podcast.find_by_title(client_podcast.title)
|
43
|
+
puts "\n- Podcast \"#{client_podcast.title}\" skipped"
|
44
|
+
next
|
45
|
+
else
|
46
|
+
puts "\n+ Podcast \"#{client_podcast.title}\""
|
47
|
+
end
|
48
|
+
|
49
|
+
client_podcast.episodes.each do |client_episode|
|
50
|
+
if client_episode.listened? && client_episode.audio?
|
51
|
+
if server_podcast.episodes.detect { |e| e.title == client_episode.title }
|
52
|
+
puts " - Episode \"#{client_episode.title}\" skipped"
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
server_episode = Server::Episode.new(nil, server_podcast.id, client_episode.title, client_episode.position * 1000, client_episode.duration * 1000)
|
57
|
+
|
58
|
+
if server_episode.save
|
59
|
+
puts " + Episode \"#{client_episode.title}\" #{(client_episode.position * 100/client_episode.duration).to_i}%"
|
60
|
+
else
|
61
|
+
puts " ? Episode \"#{client_episode.title}\" failed"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end # self.sync_podcastly
|
67
|
+
|
68
|
+
def self.start(options = {})
|
69
|
+
puts "Syncing Podcastly -> iTunes"
|
70
|
+
sync_itunes
|
71
|
+
|
72
|
+
puts "\nSyncing iTunes -> Podcastly"
|
73
|
+
sync_podcastly
|
74
|
+
end # self.start
|
75
|
+
end # Sync
|
76
|
+
end # Ipodcastly
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ipodcastly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Denis Barushev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-10 00:00:00 +02:00
|
13
|
+
default_executable: ipodcastly
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rb-appscript
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.5.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: httparty
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.4.5
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: choice
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.1.4
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rspec
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.2.9
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mocha
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.9.8
|
64
|
+
version:
|
65
|
+
description: The gem was built as a simple example to demonstrate how podcastly.com API works.
|
66
|
+
email: barushev@gmail.com
|
67
|
+
executables:
|
68
|
+
- ipodcastly
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files:
|
72
|
+
- LICENSE
|
73
|
+
- README.md
|
74
|
+
files:
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- bin/ipodcastly
|
78
|
+
- lib/ipodcastly.rb
|
79
|
+
- lib/ipodcastly/client/episode.rb
|
80
|
+
- lib/ipodcastly/client/podcast.rb
|
81
|
+
- lib/ipodcastly/server/episode.rb
|
82
|
+
- lib/ipodcastly/server/podcast.rb
|
83
|
+
- lib/ipodcastly/sync.rb
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://github.com/denis/ipodcastly
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options:
|
90
|
+
- --charset=UTF-8
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: "0"
|
98
|
+
version:
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: "0"
|
104
|
+
version:
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.3.5
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Ipodcastly is a console client for syncing local iTunes library with podcastly.com.
|
112
|
+
test_files: []
|
113
|
+
|