2 # This file is part of my.gpodder.org.
4 # my.gpodder.org is free software: you can redistribute it and/or modify it
5 # under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or (at your
7 # option) any later version.
9 # my.gpodder.org is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 # License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with my.gpodder.org. If not, see <http://www.gnu.org/licenses/>.
19 from datetime
import timedelta
, date
21 from django
.db
.models
import Avg
, Count
22 from django
.contrib
.auth
.models
import User
24 from mygpo
.utils
import daterange
, flatten
25 from mygpo
.core
.models
import Podcast
26 from mygpo
.api
.models
import Episode
, EpisodeAction
27 from mygpo
.api
.constants
import DEVICE_TYPES
28 from mygpo
import migrate
31 def listener_data(podcasts
, start_date
=date(2010, 1, 1), leap
=timedelta(days
=1)):
32 """ Returns data for the podcast listener timeseries
34 An iterator with data for each day (starting from either the first released
35 episode or the earliest listen-event) is returned, where each day
36 is reresented by a dictionary
39 * listeners: the number of listeners on that day
40 * episode: (one of) the episode(s) released on that day
43 # pre-calculate episode list, make it index-able by release-date
44 episodes
= flatten([podcast
.get_episodes() for podcast
in podcasts
])
45 episodes
= filter(lambda e
: e
.released
, episodes
)
46 episodes
= dict([(e
.released
.date(), e
) for e
in episodes
])
48 listeners
= [ list(p
.listener_count_timespan()) for p
in podcasts
]
50 # we start either at the first episode-release or the first listen-event
51 start
= min( min(episodes
.keys()), min([l
[0][0] for l
in listeners
]))
53 for d
in daterange(start
, leap
=leap
):
65 episode
= episodes
[d
] if d
in episodes
else None
67 yield dict(date
=d
, listeners
=listener_sum
, episode
=episode
)
71 def episode_listener_data(episode
, start_date
=date(2010, 1, 1), leap
=timedelta(days
=1)):
72 """ Returns data for the episode listener timeseries
74 An iterator with data for each day (starting from the first listen-event)
75 is returned, where each day is represented by a dictionary
78 * listeners: the number of listeners on that day
79 * episode: the episode, if it was released on that day, otherwise None
82 listeners
= list(episode
.listener_count_timespan())
84 # we always start at the first listen-event
85 start
= listeners
[0][0]
87 for d
in daterange(start
, leap
=leap
):
90 if listeners
and listeners
[0] and listeners
[0][0] == d
:
91 day
, l
= listeners
.pop()
95 released
= episode
.released
and episode
.released
>= d
and episode
.released
<= next
96 released_episode
= episode
if released
else None
98 yield dict(date
=d
, listeners
=l
, episode
=released_episode
)
101 def subscriber_data(podcasts
):
102 coll_data
= collections
.defaultdict(int)
104 for podcast
in podcasts
:
105 create_entry
= lambda r
: (r
.timestamp
.strftime('%y-%m'), r
.subscriber_count
)
106 data
= dict(map(create_entry
, podcast
.subscribers
))
109 coll_data
[k
] += data
[k
]
111 # create a list of {'x': label, 'y': value}
112 coll_data
= sorted([dict(x
=a
, y
=b
) for (a
, b
) in coll_data
.items()], key
=lambda x
: x
['x'])
117 def check_publisher_permission(user
, podcast
):
121 p
= migrate
.get_or_migrate_podcast(podcast
)
122 u
= migrate
.get_or_migrate_user(user
)
123 if p
.get_id() in u
.published_objects
:
129 def colour_repr(val
, max_val
, colours
):
131 returns a color representing the given value within a color gradient.
133 The color gradient is given by a list of (r, g, b) tupels. The value
134 is first located within two colors (of the list) and then approximated
135 between these two colors, based on its position within this segment.
137 if len(colours
) == 1:
140 # calculate position in the gradient; defines the segment
141 pos
= float(val
) / max_val
142 colour_nr1
= min(len(colours
)-1, int(pos
* (len(colours
)-1)))
143 colour_nr2
= min(len(colours
)-1, colour_nr1
+1)
144 colour1
= colours
[ colour_nr1
]
145 colour2
= colours
[ colour_nr2
]
150 # determine bounds of segment
151 lower_bound
= float(max_val
) / (len(colours
)-1) * colour_nr1
152 upper_bound
= min(max_val
, lower_bound
+ float(max_val
) / (len(colours
)-1))
154 # position within the segment
155 percent
= (val
- lower_bound
) / upper_bound
161 return (r1
+ r_step
* percent
, g1
+ g_step
* percent
, b1
+ b_step
* percent
)