├── README.md ├── YouTube.alfredworkflow └── source ├── Feedback.py ├── icon.png ├── info.plist ├── youtube.py └── youtube_helper.py /README.md: -------------------------------------------------------------------------------- 1 | YouTube Workflow for Alfred 2.0 2 | ====== 3 | 4 | ⚠️ YouTube's API have changed since I first developed the workflow and the workflow have not been modified to use the updated API. 5 | 6 | A workflow for Alfred 2.0 which lets the user search for YouTube videos and have them returned to Alfred. 7 | 8 | ![](http://f.cl.ly/items/0c1m1O1o202S2N0f3A0q/Sk%C3%A6rmbillede%202013-01-13%20kl.%2018.34.26.png) 9 | 10 | Usage 11 | ====== 12 | 13 | Typing `youtube` followed by a search query will show the results for the query. For example, `youtube rebecca black` will search for Rebecca Black videos. 14 | 15 | Commands 16 | ===== 17 | 18 | Below is a list of the commands that the workflow provides. Parameters in brackets are required. 19 | 20 | - **youtube (query)** or **yt s|search (query)** or **yt (query)** Searches YouTube for videos matching the query. 21 | - **yt c|channels (query)** Seaches for channels. 22 | - **yt cv|channelvideos (query)** Shows videos for the specified channel. 23 | - **yt toprated** Shows the top rated videos. 24 | - **yt topfavorited** Shows the most favorited videos. 25 | - **yt mostviewed** Shows the most viewed videos. 26 | - **yt mostpopular** Shows the most popular videos. 27 | - **yt mostrecent** Shows the most recent videos. 28 | - **yt mostdiscussed** Shows the most discussed videos. 29 | - **yt mostresponded** Shows the videos with most responds. 30 | - **yt recentlyfeatured** Shows videos which have recently been featured. 31 | 32 | If you find yourself having a hard time remembering the commands, remember that they will show up in autocompletion if you type *yt*. 33 | 34 | About 35 | ===== 36 | 37 | This workflow is developed by [@simonbs](http://twitter.com/simonbs) 38 | -------------------------------------------------------------------------------- /YouTube.alfredworkflow: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonbs/alfred-youtube-workflow/426a9f4accd5d48bc6a45ff708f93bea30dc4353/YouTube.alfredworkflow -------------------------------------------------------------------------------- /source/Feedback.py: -------------------------------------------------------------------------------- 1 | #author: Peter Okma 2 | import xml.etree.ElementTree as et 3 | 4 | class Feedback(): 5 | """Feeback used by Alfred Script Filter 6 | 7 | Usage: 8 | fb = Feedback() 9 | fb.add_item('Hello', 'World') 10 | fb.add_item('Foo', 'Bar') 11 | print fb 12 | 13 | """ 14 | 15 | def __init__(self): 16 | self.feedback = et.Element('items') 17 | 18 | def __repr__(self): 19 | """XML representation used by Alfred 20 | 21 | Returns: 22 | XML string 23 | """ 24 | return et.tostring(self.feedback) 25 | 26 | def add_item(self, title, subtitle = "", arg = "", valid = "yes", autocomplete = "", icon = "icon.png"): 27 | """ 28 | Add item to alfred Feedback 29 | 30 | Args: 31 | title(str): the title displayed by Alfred 32 | Keyword Args: 33 | subtitle(str): the subtitle displayed by Alfred 34 | arg(str): the value returned by alfred when item is selected 35 | valid(str): whether or not the entry can be selected in Alfred to trigger an action 36 | autcomplete(str): the text to be inserted if an invalid item is selected. This is only used if 'valid' is 'no' 37 | icon(str): filename of icon that Alfred will display 38 | """ 39 | item = et.SubElement(self.feedback, 'item', uid=str(len(self.feedback)), arg=arg, valid=valid, autocomplete=autocomplete) 40 | _title = et.SubElement(item, 'title') 41 | _title.text = title 42 | _sub = et.SubElement(item, 'subtitle') 43 | _sub.text = subtitle 44 | _icon = et.SubElement(item, 'icon') 45 | _icon.text = icon -------------------------------------------------------------------------------- /source/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simonbs/alfred-youtube-workflow/426a9f4accd5d48bc6a45ff708f93bea30dc4353/source/icon.png -------------------------------------------------------------------------------- /source/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | dk.simonbs.Alfred.YouTube 7 | connections 8 | 9 | 986D4B18-2B25-44D9-AB82-59B3C350FA91 10 | 11 | 12 | destinationuid 13 | 2BB6EB13-3582-43AD-8330-D52EE317087D 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | 19 | 20 | BE57A1DA-0301-42B6-B748-A4CD8C813547 21 | 22 | 23 | destinationuid 24 | 2BB6EB13-3582-43AD-8330-D52EE317087D 25 | modifiers 26 | 0 27 | modifiersubtext 28 | 29 | 30 | 31 | 32 | createdby 33 | Simon Støvring (@simonbs) 34 | description 35 | Search for YouTube videos 36 | name 37 | YouTube 38 | objects 39 | 40 | 41 | config 42 | 43 | argumenttype 44 | 0 45 | escaping 46 | 0 47 | keyword 48 | youtube 49 | script 50 | from youtube import search_videos 51 | 52 | # Use Googles default max results and order by relevance 53 | print(search_videos("{query}", 0, "relevance")) 54 | subtext 55 | Search YouTube for '{query}' 56 | title 57 | YouTube 58 | type 59 | 3 60 | withspace 61 | 62 | 63 | type 64 | alfred.workflow.input.scriptfilter 65 | uid 66 | 986D4B18-2B25-44D9-AB82-59B3C350FA91 67 | 68 | 69 | config 70 | 71 | plusspaces 72 | 73 | url 74 | {query} 75 | utf8 76 | 77 | 78 | type 79 | alfred.workflow.action.openurl 80 | uid 81 | 2BB6EB13-3582-43AD-8330-D52EE317087D 82 | 83 | 84 | config 85 | 86 | argumenttype 87 | 1 88 | escaping 89 | 0 90 | keyword 91 | yt 92 | script 93 | from youtube_helper import youtube_helper 94 | 95 | print(youtube_helper("{query}")) 96 | title 97 | YouTube 98 | type 99 | 3 100 | withspace 101 | 102 | 103 | type 104 | alfred.workflow.input.scriptfilter 105 | uid 106 | BE57A1DA-0301-42B6-B748-A4CD8C813547 107 | 108 | 109 | uidata 110 | 111 | 2BB6EB13-3582-43AD-8330-D52EE317087D 112 | 113 | ypos 114 | 80 115 | 116 | 986D4B18-2B25-44D9-AB82-59B3C350FA91 117 | 118 | ypos 119 | 10 120 | 121 | BE57A1DA-0301-42B6-B748-A4CD8C813547 122 | 123 | ypos 124 | 150 125 | 126 | 127 | webaddress 128 | http://simonbs.dk/ 129 | 130 | 131 | -------------------------------------------------------------------------------- /source/youtube.py: -------------------------------------------------------------------------------- 1 | ## 2 | # By @simonbs 3 | # http://simonbs.dk/ 4 | ## 5 | 6 | # Whether or not to show the view count in subtitle 7 | # Note that there are a few videos for which the view count is not available 8 | SUBTITLE_SHOWS_VIEW_COUNT = True 9 | 10 | # Locale 11 | LOCALE = "da_DK" 12 | 13 | ### 14 | # Don't edit below :-) 15 | ## 16 | 17 | import re 18 | import sys 19 | import urllib 20 | import json 21 | import locale 22 | from Feedback import Feedback 23 | from xml.dom import minidom 24 | 25 | # Returns the top rated videos on YouTube. 26 | def top_rated_videos(max_results = 0): 27 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/top_rated?v=2&alt=jsonc", max_results) 28 | 29 | # Returns the top favorited videos on YouTube. 30 | def top_favorited_videos(max_results = 0): 31 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/top_favorites?v=2&alt=jsonc", max_results) 32 | 33 | # Returns the most viwed videos on YouTube. 34 | def most_viewed_videos(max_results = 0): 35 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/most_viewed?v=2&alt=jsonc", max_results) 36 | 37 | # Returns the most popular videos on YouTube. 38 | def most_popular_videos(max_results = 0): 39 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/most_popular?v=2&alt=jsonc", max_results) 40 | 41 | # Returns the most recent videos on YouTube. 42 | def most_recent_videos(max_results = 0): 43 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/most_recent?v=2&alt=jsonc", max_results) 44 | 45 | # Returns the most discussed videos on YouTube. 46 | def most_discussed_videos(max_results = 0): 47 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/most_discussed?v=2&alt=jsonc", max_results) 48 | 49 | # Returns the most responded videos on YouTube. 50 | def most_responded_videos(max_results = 0): 51 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/most_responded?v=2&alt=jsonc", max_results) 52 | 53 | # Returns the recently featured videos on YouTube. 54 | def recently_featured_videos(max_results = 0): 55 | return results("https://gdata.youtube.com/feeds/api/standardfeeds/recently_featured?v=2&alt=jsonc", max_results) 56 | 57 | # Returns the videos for a given channel 58 | def channel_videos(username, max_results = 0, orderby = "published"): 59 | url = "https://gdata.youtube.com/feeds/api/users/%s/uploads?v=2&alt=jsonc&orderby=%s" % (username, orderby) 60 | return results(url, max_results) 61 | 62 | # Searches YouTube for results matching the terms and returns the results. 63 | # Supported values for orderby are 64 | # - relevance, viewCount, published, rating 65 | def search_videos(terms, max_results = 0, orderby = "relevance"): 66 | url = "https://gdata.youtube.com/feeds/api/videos?v=2&alt=jsonc&q=%s&orderby=%s" % (terms, orderby) 67 | return results(url, max_results) 68 | 69 | # Searches for channels. 70 | def search_channels(query, max_results = 0): 71 | feedback = Feedback() 72 | url = "https://gdata.youtube.com/feeds/api/channels?q=%s&v=2" % (query) 73 | url = max_results_url(url, max_results) 74 | content = content_of_url(url) 75 | dom = minidom.parseString(content) 76 | entries = dom.getElementsByTagName("entry") 77 | if len(entries) == 0: 78 | return no_results() 79 | else: 80 | for entry in entries: 81 | title = entry.getElementsByTagName("title")[0].firstChild.nodeValue 82 | summary = entry.getElementsByTagName("summary")[0].firstChild 83 | name = entry.getElementsByTagName("author")[0].firstChild.firstChild.nodeValue 84 | if summary is not None: 85 | summary = summary.data 86 | feedback.add_item(title, summary, ("http://www.youtube.com/user/%s" % name)) 87 | return feedback 88 | 89 | # Returns XML parsed results for the specified URL and maximum amount of results. 90 | def results(url, max_results): 91 | url = max_results_url(url, max_results) 92 | items = items_at_url(url) 93 | if items == None or len(items) == 0: 94 | return no_results() 95 | return xml_results(items) 96 | 97 | # Appends the maximum results to a URL. 98 | # The maximum results is only added if the value is between 1 and 50 99 | # which is the range Google allows. 100 | # If the max results falls out of this range, Googles default max results is used. 101 | def max_results_url(url, max_results): 102 | if max_results >= 1 and max_results <= 50: 103 | url = "%s&max-results=%s" % (url, max_results) 104 | return url 105 | 106 | # Loads the items at the specified URL. 107 | def items_at_url(url): 108 | content = content_of_url(url) 109 | json_response = json.loads(content) 110 | if "data" in json_response and "items" in json_response["data"]: 111 | return json_response["data"]["items"] 112 | return None 113 | 114 | # Loads the content of a URL. 115 | def content_of_url(url): 116 | conn = urllib.urlopen(url) 117 | response = conn.read() 118 | return response 119 | 120 | # Parses a list results into XML for Alfred. 121 | def xml_results(items): 122 | feedback = Feedback() 123 | for item in items: 124 | video_id = item["id"] 125 | if video_id is not None: 126 | title = item["title"] 127 | subtitle = "by %s (%s)" % (item["uploader"], seconds_to_string(item["duration"])) 128 | if SUBTITLE_SHOWS_VIEW_COUNT is True and "viewCount" in item: 129 | view_count = item["viewCount"] 130 | view_word = "view" 131 | if view_count is not 1: 132 | view_word = "views" 133 | subtitle = "%s [%s %s]" % (subtitle, locale.format("%d", view_count, grouping = True), view_word) 134 | feedback.add_item(title, subtitle, ("http://www.youtube.com/watch?v=%s" % video_id)) 135 | return feedback 136 | 137 | # Converts seconds into a string cotnaing hours, minutes and seconds and returns the string. 138 | def seconds_to_string(seconds): 139 | hours = seconds / 3600 140 | minutes = (seconds % 3600) / 60 141 | seconds = seconds % 3600 % 60 142 | result = "" 143 | if hours > 0: 144 | result = "%sh" % (hours) 145 | if minutes > 0: 146 | if hours > 0: 147 | result = "%s " % (result) 148 | result = "%s%sm" % (result, minutes) 149 | if seconds > 0: 150 | if hours > 0 and minutes == 0 or minutes > 0: 151 | result = "%s " % (result) 152 | result = "%s%ss" % (result, seconds) 153 | return result 154 | 155 | # Message returned when no results were found 156 | def no_results(): 157 | feedback = Feedback() 158 | feedback.add_item("No results found", "I'm really sorry that you had to experience this.", arg = "", valid = "no") 159 | return feedback 160 | 161 | # Configurations 162 | def config(): 163 | if SUBTITLE_SHOWS_VIEW_COUNT == True: 164 | locale.setlocale(locale.LC_ALL, LOCALE) 165 | 166 | # Make configurations 167 | config() -------------------------------------------------------------------------------- /source/youtube_helper.py: -------------------------------------------------------------------------------- 1 | ## 2 | # By @simonbs 3 | # http://simonbs.dk/ 4 | ## 5 | 6 | import youtube 7 | import string 8 | from Feedback import Feedback 9 | 10 | # Returns feedback according to query 11 | def youtube_helper(query): 12 | args = string.split(query, " ") 13 | if (args[0] == "s" or args[0] == "search") and len(args) > 1 and args[1] is not "": 14 | return youtube.search_videos(" ".join(args[1:])) 15 | elif (args[0] == "c" or args[0] == "channels") and len(args) > 1 and args[1] is not "": 16 | return youtube.search_channels(" ".join(args[1:])) 17 | elif (args[0] == "cv" or args[0] == "channelvideos") and len(args) > 1 and args[1] is not "": 18 | return youtube.channel_videos(" ".join(args[1:])) 19 | elif args[0] == "toprated": 20 | return youtube.top_rated_videos() 21 | elif args[0] == "topfavorited": 22 | return youtube.top_favorited_videos() 23 | elif args[0] == "mostviewed": 24 | return youtube.most_viewed_videos() 25 | elif args[0] == "mostpopular": 26 | return youtube.most_popular_videos() 27 | elif args[0] == "mostrecent": 28 | return youtube.most_recent_videos() 29 | elif args[0] == "mostdiscussed": 30 | return youtube.most_discussed_videos() 31 | elif args[0] == "mostresponded": 32 | return youtube.most_responded_videos() 33 | elif args[0] == "recentlyfeatured": 34 | return youtube.recently_featured_videos() 35 | elif len(args) == 0 or args[0] == "": 36 | feedback = Feedback() 37 | feedback.add_item("Search videos", "", "", "no", "s") 38 | feedback.add_item("Search channels", "", "", "no", "c") 39 | feedback.add_item("View videos on channel", "", "", "no", "cv") 40 | feedback.add_item("Top rated videos", "", "", "no", "toprated") 41 | feedback.add_item("Top favorited videos", "", "", "no", "topfavorited") 42 | feedback.add_item("Most viewed videos", "", "", "no", "mostviewed") 43 | feedback.add_item("Most popular videos", "", " videos", "no", "mostpopular") 44 | feedback.add_item("Most recent videos", "", "", "no", "mostrecent") 45 | feedback.add_item("Most discussed videos", "", "", "no", "mostdiscussed") 46 | feedback.add_item("Most responded videos", "", "", "no", "mostresponded") 47 | feedback.add_item("Recently featured videos", "", "", "no", "recentlyfeatured") 48 | return feedback 49 | else: 50 | return youtube.search_videos(" ".join(args)) 51 | return None --------------------------------------------------------------------------------