Есть еще один процесс, который занимает мое время. Это отслеживание музыкальных новинок. Частично с этим помогает LastFm. Но попользовавшись им 2 года осознал недостатки:
шел в магазин за диском. Да-да, все туда же - сначала за торрентами.
Как его сделать? За обедом обсуждал это с коллегами где они подкинули идею использовать twitter. Что-то вроде - заретвитил сообщение о раздаче - значит её действительно нужно скачать. Подписываешься на робота и читаешь его публикации - если есть стоящее - ретвитишь, и робот раздачу скачает.
Дальнейшее развитие идеи
- уведомление приходит достаточно поздно (вплоть до 2-3 месяцев) с момента выхода нового альбома. Может совсем не прийти
- приходит всякий шлак (синглы, компиляции и т.п.) - т.е. не новый материал
Почему бы не автоматизировать и этот процесс? Подписываемся (как и в кино) на раздачи в интересующих жанрах (и, конечно, в loseless, т.к. mp3 это моветон), анализируем раздачи используя информацию из LastFm, скачиваем, profit! Но не так всё просто как с фильмами. В музыкальной индустрии на один действительно новый релиз приходится несколько перекомпиляций, ремастерингов, синглов, переизданий, original press и прочего. То есть полностью автоматизировать скачку означает достаточно быстро забить музыкальную библиотеку дубликатами в которых потом не разобраться. Нужен человеческий фильтр.
Как его сделать? За обедом обсуждал это с коллегами где они подкинули идею использовать twitter. Что-то вроде - заретвитил сообщение о раздаче - значит её действительно нужно скачать. Подписываешься на робота и читаешь его публикации - если есть стоящее - ретвитишь, и робот раздачу скачает.
Итак, вырисовалась следующая последовательность:
- Получаем новые раздачи в нужных жанрах (да-да, я снова о ру-трекере)
- Загружаем исполнителей и уже прослушанные альбомы из LastFm
- Если раздача нужного исполнителя и такой альбом еще не существует в базе - публиковать в твиттер
- Если сообщение ре-твитится нужным пользователем - ставить на закачку
feedparser никуда не изчез - используем его.
Обертки LastFm API для Python я нашел 2: pylast и lastfmapi. Первый оказался достаточно мощным (поддерживает скробблинг), но, к сожалению использует не все параметры вызовов - полагается на умолчания. Я смог это обойти отредактировав исходники, но потом решил что это неприемлемо и переключился на второй - lastfmapi. Он более простой (кажется автор написал всего пару месяцев назад) но, как следствие, мощный.
В процессе работы выяснилась интересная особенность LastFm. Если у артиста несколько имен (например Земфира и Zемфира) - то в списке артистов вернется только "правильная" версия имени. В списке же альбомов имя артиста может быть и другим. Пока что я отказался от умного анализа таких совпадений и просто складываю оба варианта себе в фильтр.
Так как раздачи менее формальны, чем в фильмах, парсить имя артиста и имя альбома сложнее и примерно у 5% раздач имя альбома парсится неправильно. Это некритично для ненужных исполнителей, а у нужных я лучше лишний раз глазами просмотрю.
Идею с ретвитом я передумал, так как твиттером не пользуюсь а интересные мне микроблоги читаю как всегда. Зато в Google Reader (пока еще) есть замечательная кнопка "share" - которая rss-пост копирует в мой собственный rss-поток (его вы можете наблюдать в колонке слева этого блога). Так как я всё читаю через Google Reader и его айфонный аватар - MobileRSS - это неплохой способ. Подписываюсь на собственного робота в Google Reader и читаю его твиты. Если есть стоящая музыкальная раздача - жму кнопку "share" и робот эту раздачу скачает.
Итак, завершающий шаг оказался донельзя простым. Робот подписывается на мой rss поток из пошареных постов и если обнаруживает там собственный твит - ставит его на закачку. Здесь пришлось продумать айдишники раздачи и прикрутить маркер уже скачанных раздач.
Результат все там же
Результат все там же
1 __author__ = 'Smog' 2 3 #standard packages 4 import re 5 6 #additional packages 7 import feedparser, lastfmapi 8 9 environment = { 10 'lastfm_username' : "", 11 'lastfm_key': "", 12 'lastfm_secret': "" 13 } 14 15 #------------------------- TORRENT ALBUM ------------------------------------------------------------------------------- 16 17 class TorrentAlbum: 18 def __init__(self, rssEntry): 19 self.topicURL = rssEntry.link 20 self.origTitle = rssEntry.title 21 self.topic = self.topicURL[len('http://rutracker.org/forum/viewtopic.php?'):] 22 23 m = re.search('(\) [^\-]+\-)', rssEntry.title) 24 # print rssEntry.title 25 if (m != None): 26 self.artist = m.group(0)[2:-1].strip() 27 start = m.regs[0][1] 28 # print rssEntry.title[start:] 29 30 m = re.search('(.)+\d\d\d\d', rssEntry.title[start:]) 31 32 if ( m != None): 33 self.title = m.group(0)[:-4] 34 rightB = len(self.title) 35 for i in range(len(self.title)): 36 sym = self.title[-1 * i] 37 if (sym.isalpha() or sym.isdigit()): 38 rightB = -1 * i 39 break 40 41 self.title = self.title[:rightB + 1].strip() 42 else: 43 print 'NO ALBUM: ' + rssEntry.title 44 self.title = 'None' 45 46 else: 47 print 'NO ARTIST: ' + rssEntry.title 48 self.artist = 'None' 49 self.title = 'None' 50 51 print self.artist + '\t' + self.title + '\t' + self.topicURL 52 53 def getTweet(self, bitly): 54 torrentLink = bitly.shorten(longUrl=self.topicURL)['url'] 55 tail = " " + torrentLink + " smbid=" + self.topic + " #music" 56 return self.origTitle[:140 - len(tail)] + tail 57 58 def __str__(self): 59 result = map(lambda x: x + "=" + unicode(self.__dict__.get(x)), self.__dict__.keys()) 60 result.sort() 61 return result.__str__() 62 63 #------------------------- LAST FM AGENT ------------------------------------------------------------------------------- 64 65 class LastFmAgent: 66 def __init__(self): 67 api = lastfmapi.LastFmApi(environment.get('lastfm_key')) 68 69 artists = [] 70 totalPages = 1 71 page = 1 72 print 'Loading artists...' 73 74 while (page <= totalPages): 75 jsonArtists = api.user_getTopArtists(user=environment.get('lastfm_username'), period='overall', limit=100, page=page) 76 totalPages = int(jsonArtists['topartists']['@attr']['totalPages']) 77 page = int(jsonArtists['topartists']['@attr']['page']) + 1 78 79 weightedArtists = filter(lambda a: int(a['playcount']) > 15, jsonArtists['topartists']['artist']) 80 artists.extend(map(lambda a: a['name'].lower(), weightedArtists)) 81 print 'Page ' + str(page) + ' / ' + str(totalPages) 82 83 print 'Found ' + str(len(artists)) + ' LastFm artists' 84 self.artistAlbums = {} 85 for a in artists: 86 self.artistAlbums[a] = [] 87 88 totalPages = 1 89 page = 1 90 print 'Loading albums...' 91 92 while (page <= totalPages): 93 jsonAlbums = api.user_getTopAlbums(user=environment.get('lastfm_username'), period='overall', limit=100, page=page) 94 totalPages = int(jsonAlbums['topalbums']['@attr']['totalPages']) 95 page = int(jsonAlbums['topalbums']['@attr']['page']) + 1 96 97 albums = filter(lambda a: a['playcount'] > 10, jsonAlbums['topalbums']['album']) 98 99 for a in albums: 100 if (self.artistAlbums.has_key(a['artist']['name'].lower())): 101 self.artistAlbums[a['artist']['name'].lower()].append(a['name'].lower()) 102 else: 103 self.artistAlbums[a['artist']['name'].lower()] = [a['name'].lower()] 104 105 print 'Page ' + str(page) + ' / ' + str(totalPages) 106 107 print 'Found ' + str(len(self.artistAlbums.values())) + ' LastFm albums' 108 109 110 def isWorthPublishing(self, album): 111 if not (self.artistAlbums.has_key(album.artist.lower())): 112 return False 113 114 return self.artistAlbums.get(album.artist.lower()).count(album.title.lower()) == 0 115 116 #------------------------- MUSIC ANALYSER MAIN ------------------------------------------------------------------------- 117 118 def checkAndPublishMusic(): 119 feeds = [ 120 'http://feed.rutracker.org/atom/f/737.atom', #http://rutracker.org/forum/viewforum.php?f=737&start=100 121 'http://feed.rutracker.org/atom/f/1702.atom', 122 'http://feed.rutracker.org/atom/f/739.atom', 123 'http://feed.rutracker.org/atom/f/1706.atom', 124 'http://feed.rutracker.org/atom/f/1704.atom', 125 'http://feed.rutracker.org/atom/f/1708.atom', 126 'http://feed.rutracker.org/atom/f/1726.atom', 127 'http://feed.rutracker.org/atom/f/1724.atom', 128 'http://feed.rutracker.org/atom/f/1744.atom', 129 'http://feed.rutracker.org/atom/f/1748.atom', 130 'http://feed.rutracker.org/atom/f/1742.atom', 131 'http://feed.rutracker.org/atom/f/1857.atom' 132 ] 133 134 print 'Getting LastFM statistics...' 135 lfmAgent = LastFmAgent() 136 137 print '\nSigning in to Twitter...' 138 tw = TwitterAgent() 139 processedAlbums = tw.getProcessedItemsIds() 140 141 print '\nLoading feeds...' 142 for feed in feeds: 143 f = feedparser.parse(feed) 144 print "\n\nLoaded " + f.feed.title + "\n" 145 146 albums = map(lambda x: TorrentAlbum(x), f.entries) 147 albums = filter(lambda x: lfmAgent.isWorthPublishing(x) and x.topic not in processedAlbums, albums) 148 149 if len(albums) > 0: 150 for a in albums: 151 print 'RECOMMENDED TO DOWNLOAD ' + a.origTitle 152 tw.tweet(a.getTweet(tw.bitly)) 153 154 155 checkAndPublishMusic() 156
Дальнейшее развитие идеи
- Конвертировать Image+CUE раздачи в треки (по-файлам);
- Создавать mp3 версию раздачи
- Загружать ее в iTunes
- Добавлять в iTunes плейлист (?)
Комментариев нет:
Отправить комментарий