Check-in [404f0b4329]
Overview
Comment: | Youtube video browsing channel replaces DMOZ listings. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
404f0b43295fd1c592358215ac60053d |
User & Date: | mario on 2014-05-26 03:00:27 |
Original Comment: | Youtube video browsing channel replaces DMOZ listings. |
Other Links: | manifest | tags |
Context
2014-05-26
| ||
14:05 | Fix ProgressBar for Py2, don't use default steps in HTTP retrieval. check-in: c5251618b3 user: mario tags: trunk | |
03:00 | Youtube video browsing channel replaces DMOZ listings. check-in: 404f0b4329 user: mario tags: trunk | |
2014-05-25
| ||
17:23 | Simplify streamedit_ by reusing config_dialog.load_config() (Also allows to use a liststore table later..) check-in: 927dc82086 user: mario tags: trunk | |
Changes
Deleted channels/google.png version [04e0c74ba2].
cannot compute difference between binary files
Deleted channels/google.py version [67db911f9e].
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Added channels/youtube.png version [f165f875e1].
cannot compute difference between binary files
Added channels/youtube.py version [c4f0c5f1c6].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | # encoding: UTF-8 # api: streamtuner2 # title: Youtube # description: Channel, playlist and video browsing for youtube. # type: channel # version: 0.1 # category: video # priority: optional # suggests: youtube-dl # requires: ahttp # # # Lists recently popular youtube videos by category or channels. # # Introduces the faux MIME type "video/youtube" for player and recording # configuration; both utilizing `youtube-dl`. But VLC can consume Youtube # URLs directly anyhow. # # For now custom channel names must be configured in the settings dialog # text entry, and applied using Channel > Update categories.. # # # INTERNA # # The Youtube v3.0 API is quite longwinded. Here the .api() call shadows # a few of the details. # While .wrap3() unpacks the various variations of where the video IDs # get hidden in the result sets. # Google uses some quote/billing algorithm for all queries. It seems # sufficient for Streamtuner2 for now, as the fields= JSON filter strips # a lot of uneeded data. (Clever idea, but probably incurs more processing # effort on Googles servers than it actually saves bandwidth, but hey..) # # # EXAMPLES # # api("videos", chart="mostPopular") # api("search", chart="mostPopular", videoCategoryId=10, order="date", type="video") # api("channels", categoryId=10) # api("search", topicId="/m/064t9", type="video") # # Discovery # # videoCat Music id= 10 # guideCat Music id= GCTXVzaWM channelid= UCBR8-60-B28hp2BmDPdntcQ # topicId Music mid= /m/0kpv0g # # from config import * from channels import * import ahttp import json # Youtube class youtube (ChannelPlugin): # description title = "Youtube" module = "youtube" homepage = "http://www.youtube.com/" listformat = "url/youtube" fmt = "video/youtube" titles = dict( genre="Channel", title="Title", playing="Playlist", bitrate=False, listeners=False ) # API config service = { 2: [ "http://gdata.youtube.com/", { "v": 2, "alt": "json", "max-results": 50, } ], 3: [ "https://www.googleapis.com/youtube/v3/", { "key": "AIzaSyAkbLSLn1VgsdFXCJjjdZtLd6W8RqtL4Ag", "maxResults": 50, "part": "id,snippet", "fields": "pageInfo,nextPageToken,items(id,snippet(title,thumbnails/default/url,channelTitle))", } ] } categories = [ "mostPopular", ["Music", "Comedy", "Movies", "Shows", "Short Movies", "Trailers", "Film & Animation", "Entertainment", "News & Politics", "Sci-Fi/Fantasy"], "topics", ["music", "pop", "music video"], "my channels", ["Key of Awesome", "Pentatonix"] ] # plugin settings config = [ { "name": "youtube_channels", "type": "text", "value": "Key Of Awesome, Pentatonix", "description": "Preferred channels to list videos from.", "category": "select", }, { "name": "youtube_region", "type": "select", "select": "=No Region|AR=Argentina|AU=Australia|AT=Austria|BE=Belgium|BR=Brazil|CA=Canada|CL=Chile|CO=Colombia|CZ=Czech Republic|EG=Egypt|FR=France|DE=Germany|GB=Great Britain|HK=Hong Kong|HU=Hungary|IN=India|IE=Ireland|IL=Israel|IT=Italy|JP=Japan|JO=Jordan|MY=Malaysia|MX=Mexico|MA=Morocco|NL=Netherlands|NZ=New Zealand|PE=Peru|PH=Philippines|PL=Poland|RU=Russia|SA=Saudi Arabia|SG=Singapore|ZA=South Africa|KR=South Korea|ES=Spain|SE=Sweden|CH=Switzerland|TW=Taiwan|AE=United Arab Emirates|US=United States", "value": "UK", "description": "Filter by region id.", "category": "auth", }, ] # from GET https://www.googleapis.com/youtube/v3/videoCategories?part=id%2Csnippet& videocat_id = { "Film & Animation": 1, "Autos & Vehicles": 2, "Music": 10, "Pets & Animals": 15, "Sports": 17, "Short Movies": 18, "Travel & Events": 19, "Gaming": 20, "Videoblogging": 21, "People & Blogs": 22, "Comedy": 34, "Entertainment": 24, "News & Politics": 25, "Howto & Style": 26, "Education": 27, "Science & Technology": 28, "Nonprofits & Activism": 29, "Movies": 30, "Anime/Animation": 31, "Action/Adventure": 32, "Classics": 33, "Documentary": 35, "Drama": 36, "Family": 37, "Foreign": 38, "Horror": 39, "Sci-Fi/Fantasy": 40, "Thriller": 41, "Shorts": 42, "Shows": 43, "Trailers": 44, } # Freebase topics topic_id = { "music": "/m/0kpv0g", "pop": "/m/064t9", "music video": "/m/05k5h7g", } # just a static list for now def update_categories(self): i = self.categories.index("my channels") + 1 self.categories[i] = [ title.strip() for title in conf.youtube_channels.split(",") ] # retrieve and parse def update_streams(self, cat, force=0, search=None): entries = [] channels = self.categories[self.categories.index("my channels") + 1] # Most Popular if cat == "mostPopular": #for row in self.api("feeds/api/standardfeeds/%s/most_popular"%conf.youtube_region, ver=2): # entries.append(self.wrap2(row)) for row in self.api("videos", chart="mostPopular", regionCode=conf.youtube_region): entries.append( self.wrap3(row, {"genre": "mostPopular"}) ) # Categories elif cat in self.videocat_id: for row in self.api("search", chart="mostPopular", videoCategoryId=self.videocat_id[cat], order="date", type="video"): entries.append( self.wrap3(row, {"genre": cat}) ) # Topics elif cat in self.topic_id: for row in self.api("search", order="date", regionCode=conf.youtube_region, topicId=self.topic_id[cat], type="video"): entries.append( self.wrap3(row, {"genre": cat}) ) # My Channels # - searches channel id for given title # - iterates over playlist # - then over playlistitems to get videos elif cat in channels: # channel id, e.g. UCEmCXnbNYz-MOtXi3lZ7W1Q UC = self.channel_id(cat) # playlist for i,playlist in enumerate(self.api("playlists", fields="items(id,snippet/title)", channelId=UC, maxResults=15)): # items (videos) for row in self.api("playlistItems", playlistId=playlist["id"], fields="items(snippet(title,resourceId/videoId,description))"): entries.append(self.wrap3(row, {"genre": cat, "playing": playlist["snippet"]["title"]})) self.update_streams_partially_done(entries) self.parent.status(i / 15.0) # plain search request for videos elif search is not None: for row in self.api("search", type="video", regionCode=conf.youtube_region, q=search): entries.append( self.wrap3(row, {"genre": ""}) ) # empty entries else: entries = [dict(title="Placeholder for subcategories", genre="./.", playing="./.", url="http://youtube.com/")] # done return entries # Search for channel name: def channel_id(self, title): id = self.channel2id.get(title) if not id: data = self.api("search", part="id", type="channel", q=title) if data: id = data[0]["id"]["channelId"] self.channel2id[title] = id return id channel2id = {} #-- Retrieve Youtube API query results # def api(self, method, ver=3, pages=5, **params): items = [] # URL and default parameters (base_url, defaults) = self.service[ver] params = dict(defaults.items() + params.items()) # Retrieve data set while pages > 0: j = ahttp.get(base_url + method, params=params) __print__(dbg.DATA, j) if j: # json decode data = json.loads(j) # extract items if "items" in data: items += data["items"] elif "feed" in data: items += data["feed"]["entry"] else: pages = 0 # Continue to load results? if items >= conf.max_streams: pages = 0 elif "pageInfo" in data and data["pageInfo"]["totalResults"] < 50: pages = 0 elif "nextPageToken" in data: params["pageToken"] = data["nextPageToken"] pages -= 1 else: pages = 0 self.parent.status(pages / 10.0) return items # Wrap API 3.0 result into streams row def wrap3(self, row, data): # Video id if "id" in row: # plain /video queries id = row["id"] # for /search queries if type(row["id"]) is dict: id = id["videoId"] # for /playlistItems elif "resourceId" in row["snippet"]: id = row["snippet"]["resourceId"]["videoId"] data.update(dict( url = "http://youtube.com/v/" + id, homepage = "https://youtube.com/watch?v=" + id, format = self.fmt, title = row["snippet"]["title"], )) # optional values if "playing" not in data: data["playing"] = row["snippet"]["channelTitle"] if "description" in row["snippet"]: data["description"] = row["snippet"]["description"], return data # API version 2.0s jsonified XML needs different unpacking: def wrap2(self, row): __print__(dbg.DATA, row) return dict( genre = row["category"][1]["term"], title = row["title"]["$t"], playing = row["author"][0]["name"]["$t"], format = self.fmt, url = row["content"]["src"].split("?")[0], homepage = row["media$group"]["media$player"]["url"], image = row["media$group"]["media$thumbnail"][0]["url"], ) |