1e838ea05ec369a23b7e2016f52af830b5a8602f
[i_like_pandora.git] / likes_pandora.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 __author__ = ("Dylan Lloyd <dylan@psu.edu>")
5 __license__ = "BSD"
6
7 # SETTINGS
8
9 USER = 'alphabethos' # pandora account name http://pandora.com/people/<USER>
10 DIR = '/home/dylan/pandora/' # where to download the videos - will not be automatically created
11 YT_DL = '/usr/bin/youtube-dl' # Path to youtube-dl
12 NOTIFICATIONS = True # False
13 DEFAULT_ICON ='/usr/share/icons/gnome/48x48/mimetypes/gnome-mime-application-x-shockwave-flash.png' # for notifications
14 YT_OPT = '--no-progress --ignore-errors --continue --max-quality=22 -o "%(stitle)s---%(id)s.%(ext)s"'
15 # END OF SETTINGS
16
17 from BeautifulSoup import BeautifulSoup
18 import urllib
19 import urllib2
20 import os
21 import re
22 import copy
23 import shlex, subprocess
24
25 if NOTIFICATIONS:
26 import pynotify
27 import hashlib
28 import tempfile
29 import string
30
31 def fetch_stations(user):
32 """ This takes a pandora username and returns the a list of the station tokens that the user is subscribed to. """
33 stations = []
34 page = urllib.urlopen('http://www.pandora.com/favorites/profile_tablerows_station.vm?webname=' + USER)
35 page = BeautifulSoup(page)
36 table = page.findAll('div', attrs={'class':'station_table_row'})
37 for row in table:
38 if row.find('a'):
39 for attr, value in row.find('a').attrs:
40 if attr == 'href':
41 stations.append(value[10:])
42 return stations
43
44 def fetch_tracks(stations):
45 """ Takes a list of station tokens and returns a list of Title + Artist strings.
46 """
47 search_strings = []
48 for station in stations:
49 page = urllib.urlopen('http://www.pandora.com/favorites/station_tablerows_thumb_up.vm?token=' + station + '&sort_col=thumbsUpDate')
50 page = BeautifulSoup(page)
51 titles = []
52 artists = []
53 for span in page.findAll('span', attrs={'class':'track_title'}):
54 for attr, value in span.attrs:
55 if attr == 'tracktitle':
56 titles.append(value)
57 for anchor in page.findAll('a'):
58 artists.append(anchor.string)
59 if len(titles) == len(artists):
60 i = 0
61 for title in titles:
62 search_string = title + ' ' + artists[i]
63 search_strings.append(search_string)
64 i += 1
65 else:
66 pass ## ERROR
67 return search_strings
68
69 def search_youtube(search_strings):
70 """ This takes a list of search strings and tries to find the first result. It returns a list of the youtube video ids of those results.
71 """
72 video_list = []
73 for search_string in search_strings:
74 search_url = 'http://youtube.com/results?search_query=' + urllib.quote_plus(search_string)
75 page = urllib.urlopen(search_url)
76 page = BeautifulSoup(page)
77 result = page.find('div', attrs={'class':'video-main-content'})
78 if result == None:
79 print 'odd feedback for search, could not find div at ', search_url
80 continue
81 for attr, value in result.attrs:
82 if attr == 'id' and len(value[19:]) == 11:
83 video_list.append(value[19:])
84 elif attr == 'id':
85 print 'odd feedback for search', search_url, " : ", value[19:]
86 return video_list
87
88
89 def check_for_existing(video_list):
90 """ Checks the download-folder for existing videos with same id and removes from video_list. """
91 filelist = os.listdir(DIR)
92 i = 0
93 for video in copy.deepcopy(video_list):
94 for files in filelist:
95 if re.search(video,files):
96 del video_list[i]
97 i -= 1
98 i += 1
99 return video_list
100
101 def fetch_videos(video_list):
102 """ Uses subprocess to trigger a download using youtube-dl of the list created earlier, and triggers notifications if enabled. """
103 os.chdir(DIR)
104 args = shlex.split(YT_DL + ' ' + YT_OPT)
105 if NOTIFICATIONS: regex = re.compile("\[download\] Destination: (.+)")
106 for video in video_list:
107 if video:
108 thread = subprocess.Popen(args + ["http://youtube.com/watch?v=" + video], stdout=subprocess.PIPE)
109 output = thread.stdout.read()
110 if NOTIFICATIONS:
111 video_file = regex.findall(output)
112 if len(video_file) == 0:
113 break
114 thumbnail = hashlib.md5('file://' + DIR + video_file[0]).hexdigest() + '.png'
115 # Two '/'s instead of three because the path is
116 # absolute; I'm not sure how this'd work on windows.
117 title, sep, vid_id = video_file[0].rpartition('---')
118 title = string.replace(title, '_', ' ')
119 thumbnail = os.path.join(os.path.expanduser('~/.thumbnails/normal'), thumbnail)
120 if not os.path.isfile(thumbnail):
121 opener = urllib2.build_opener()
122 try:
123 page = opener.open('http://img.youtube.com/vi/' + video + '/1.jpg')
124 thumb = page.read()
125 # The thumbnail really should be saved to
126 # ~/.thumbnails/normal (Thumbnail Managing
127 # Standard)
128 # [http://jens.triq.net/thumbnail-spec/]
129 # As others have had problems anyway
130 # (http://mail.gnome.org/archives/gnome-list/2010-October/msg00009.html)
131 # I decided not to bother at the moment.
132 temp = tempfile.NamedTemporaryFile(suffix='.jpg')
133 temp.write(thumb)
134 temp.flush()
135 note = pynotify.Notification(title, 'video downloaded', temp.name)
136 except:
137 note = pynotify.Notification(title, 'video downloaded', DEFAULT_ICON)
138 else:
139 # Generally, this will never happen, because the
140 # video is a new file.
141 note = pynotify.Notification(title, 'video downloaded', thumbnail)
142 note.show()
143
144
145 def main():
146 stations = fetch_stations(USER)
147 if len(stations) == 0:
148 print 'are you sure your pandora profile is public?'
149 search_strings = fetch_tracks(stations)
150 videos = search_youtube(search_strings)
151 videos = check_for_existing(videos)
152 fetch_videos(videos)
153
154 if __name__ == "__main__":
155 main()