Привет всему сообществу Хабра!
Наверняка многие по дороге 'дом' <--> 'работа' слушают в пути музыку со своего Android-фона. Я тоже частенько досыпаю в метро под бодрый breaks лишние 10-20 минут. В очередной поездке до места оперативного базирования, наслаждаясь треками, которым уже «сто лет в обед» сделал себе заметку в голове, что нужно бы обновить вечерком фонотеку. Конечно же, заметка была благополучно забыта в вихрях рабочего дня, и на следующее утро я опять ехал с заезженной пластинкой. Немного прикинул и решил, что надо бы автоматизировать этот процесс, дабы исключить мое богомерзкое влияние на дело автоматизации.
Заинтересованным гражданам — добро пожаловать под кат.
Выбор стратегии был определен еще во время поездки, благо небольшой «черный гробик» aka сервер на Atom`е, с Ubuntu на борту был в моем домашнем распоряжении. Решение было такое: некий демонёнок стримит поток интернет-радио на сервере, а скрипт на змеюке занимается всем остальным: рассортировывает по папкам треки, создает плейлисты, и каким-либо образом сливает все добро на смартфон, желательно ночью.
После общей концепции встал выбор конкретной реализации задуманного. Итак, приступим по порядку:
Запись потока вещания. В качестве «захватчика» выбрал небольшую консольную утилиту streamripper. Умеет делать, все что нужно и даже больше. Достаточно передать ей адрес потока и еще несколько настроек: где хранить треки и как их обзывать.
Сам я по роду своей деятельности непосредственно не связан с IT-инфраструктурой. Просто иногда, в случае каких либо стандартных действий, проще написать скрипт или обработчик, и со спокойно совестью заниматься основной работой, а не тратить ее на рутину. Терпеть не могу, когда вдруг оказывается нужно проверить 100 позиций и узнать дату доставки у поставщика. При этом конечно же поставщик, как искренне заботливый и заинтересованный в продажах вендор, решительно не вступает в 21 век, оставляя одно поле для одной позиции в форме на сайте. Массовая обработка запросов и синхронизация БД? Не, не слышали) Через это, проще поднять Апач и натравить на это безобразие curl, чем сидеть битый час и заниматься «копи-пастом». Прикинув, что же будет лучше, из моего багажа знаний выбрал python. На нем и будет основываться весь скелет, который соберет воедино систему синхронизации данных.
Далее — каким образом перекачивать информацию на смартфон? Вообще, поскольку Android сам является мегапотомком *никсов, проблем с выбором интерфейса передачи быть не должно. Быстро прошуршав гугломаркет, нашел все, что необходимо в качестве серверной части для Androida. Оказалось, что имеется в наличии Samba, SSHfs и стандартный FTP. После нескольких тестовых перекачек с сервера на андроид 2-3 Гб треков остановился на FTP, хотя приятней конечно же было работать по SSHfs. Только вот незадача — то ли мой смарт(перешитый на Android HTC HD2) не справлялся с нагрузкой, то ли была криво реализована передача ключей или еще что, но как факт у меня остался осадочек и по Самбе, и по ssh. Периодически случались обрывы записи, особенно на больших объемах (over 0.7-1Gb), терялись ключи, либо неверно выдавались права доступа для юзера sshfs. В общем, с учетом стабильного FTP, разбираться баг ли это разработчиков или моих рук решительно расхотелось и остался последний момент. FTP сервер должен был подниматься на телефоне либо по команде с сервера, либо по собственному планировщику. В любом случае, нужно было разобраться с консолью в Андроиде и передать приложению intent.action на запуск сервера. И вот тут меня поджидало разочарование — ни один из клиентов (может плохо искал?) не имел ничего похожего в описании AndroidManifest.xml. Т.е. сам запуск приложения был возможен, а вот старт сервера — уже нет. Мне просто случайно повезло, что я заметил в функциях к замечательному приложению FTPServer такую особенность как «стартовать FTP — сервер, при подключении к выбранной wi-fi сети». Вот теперь и эта задача была решена.
И последний вопрос. Он больше связан с самим устройством операционной системы Android, а именно с ее высоким энергопотреблением. Сам FTP сервер все время держит включенным wi-fi и дисплей, не давая таким образом уходить в сон устройству, что и правильно, иначе Android отключит сеть и FTP сервер будет недоступен. Таким образом, задача свелась к следующему: запускать FTP, при наличии зарядки, ночью и при нахождении в зоне действия домашнего wi-fi. Вспомнилась когда-прочитанная заметка на Хабре о программе Tasker, которая поддерживала написание несложных скриптов и перекл��чение функций телефона в зависимости от разных условий. Программа была немедленно скачана, изучена и опробована. Результат удовлетворил более чем.
Как итог, в общем связка действовала таким образом: со стороны смартфона ночью (за несколько минут до старта скрипта на сервере), при наличии зарядки и доступности домашней сети стартовал FTPServer, который находился в сети 2 часа, после чего телефон уходил на ребут, что бы обновить медиатеку файлов и сделать доступными плейлисты. Со стороны сервера Ubuntu происходила запись потока с помощью streamrippera, а с помощью крона так же ночью стартовал скрипт, который делал следующее:
Такая небольшая домашняя автоматизация живет у меня уже пару недель и серъезных багов замечено пока не было. Особенно приятно вставать утром, нацепить наушники и радоваться, что новой музыки вдоволь и весь процесс переноса записей происходит в фоне для меня, как человека.
Собственно, вот такая небольшая история не столько о музыке, а о тех возможностях, которые нам сегодня дают современные разработки для того, что бы сделать жизнь более удобной, оставляя время на творчество, а не на рутину.
Пара рекомендаций и замечаний:
Ниже предоставлю текст скрипта, в общем там довольно много пояснений, должно быть понятно «на лету».
Наверняка многие по дороге 'дом' <--> 'работа' слушают в пути музыку со своего Android-фона. Я тоже частенько досыпаю в метро под бодрый breaks лишние 10-20 минут. В очередной поездке до места оперативного базирования, наслаждаясь треками, которым уже «сто лет в обед» сделал себе заметку в голове, что нужно бы обновить вечерком фонотеку. Конечно же, заметка была благополучно забыта в вихрях рабочего дня, и на следующее утро я опять ехал с заезженной пластинкой. Немного прикинул и решил, что надо бы автоматизировать этот процесс, дабы исключить мое богомерзкое влияние на дело автоматизации.
Заинтересованным гражданам — добро пожаловать под кат.
Выбор стратегии был определен еще во время поездки, благо небольшой «черный гробик» aka сервер на Atom`е, с Ubuntu на борту был в моем домашнем распоряжении. Решение было такое: некий демонёнок стримит поток интернет-радио на сервере, а скрипт на змеюке занимается всем остальным: рассортировывает по папкам треки, создает плейлисты, и каким-либо образом сливает все добро на смартфон, желательно ночью.
После общей концепции встал выбор конкретной реализации задуманного. Итак, приступим по порядку:
Внимание! Все разговоры записываются.
Запись потока вещания. В качестве «захватчика» выбрал небольшую консольную утилиту streamripper. Умеет делать, все что нужно и даже больше. Достаточно передать ей адрес потока и еще несколько настроек: где хранить треки и как их обзывать.
Используем Python
Сам я по роду своей деятельности непосредственно не связан с IT-инфраструктурой. Просто иногда, в случае каких либо стандартных действий, проще написать скрипт или обработчик, и со спокойно совестью заниматься основной работой, а не тратить ее на рутину. Терпеть не могу, когда вдруг оказывается нужно проверить 100 позиций и узнать дату доставки у поставщика. При этом конечно же поставщик, как искренне заботливый и заинтересованный в продажах вендор, решительно не вступает в 21 век, оставляя одно поле для одной позиции в форме на сайте. Массовая обработка запросов и синхронизация БД? Не, не слышали) Через это, проще поднять Апач и натравить на это безобразие curl, чем сидеть битый час и заниматься «копи-пастом». Прикинув, что же будет лучше, из моего багажа знаний выбрал python. На нем и будет основываться весь скелет, который соберет воедино систему синхронизации данных.
Samba, FTP или SSHfs
Далее — каким образом перекачивать информацию на смартфон? Вообще, поскольку Android сам является мегапотомком *никсов, проблем с выбором интерфейса передачи быть не должно. Быстро прошуршав гугломаркет, нашел все, что необходимо в качестве серверной части для Androida. Оказалось, что имеется в наличии Samba, SSHfs и стандартный FTP. После нескольких тестовых перекачек с сервера на андроид 2-3 Гб треков остановился на FTP, хотя приятней конечно же было работать по SSHfs. Только вот незадача — то ли мой смарт(перешитый на Android HTC HD2) не справлялся с нагрузкой, то ли была криво реализована передача ключей или еще что, но как факт у меня остался осадочек и по Самбе, и по ssh. Периодически случались обрывы записи, особенно на больших объемах (over 0.7-1Gb), терялись ключи, либо неверно выдавались права доступа для юзера sshfs. В общем, с учетом стабильного FTP, разбираться баг ли это разработчиков или моих рук решительно расхотелось и остался последний момент. FTP сервер должен был подниматься на телефоне либо по команде с сервера, либо по собственному планировщику. В любом случае, нужно было разобраться с консолью в Андроиде и передать приложению intent.action на запуск сервера. И вот тут меня поджидало разочарование — ни один из клиентов (может плохо искал?) не имел ничего похожего в описании AndroidManifest.xml. Т.е. сам запуск приложения был возможен, а вот старт сервера — уже нет. Мне просто случайно повезло, что я заметил в функциях к замечательному приложению FTPServer такую особенность как «стартовать FTP — сервер, при подключении к выбранной wi-fi сети». Вот теперь и эта задача была решена.
Android
И последний вопрос. Он больше связан с самим устройством операционной системы Android, а именно с ее высоким энергопотреблением. Сам FTP сервер все время держит включенным wi-fi и дисплей, не давая таким образом уходить в сон устройству, что и правильно, иначе Android отключит сеть и FTP сервер будет недоступен. Таким образом, задача свелась к следующему: запускать FTP, при наличии зарядки, ночью и при нахождении в зоне действия домашнего wi-fi. Вспомнилась когда-прочитанная заметка на Хабре о программе Tasker, которая поддерживала написание несложных скриптов и перекл��чение функций телефона в зависимости от разных условий. Программа была немедленно скачана, изучена и опробована. Результат удовлетворил более чем.
Итоги
Как итог, в общем связка действовала таким образом: со стороны смартфона ночью (за несколько минут до старта скрипта на сервере), при наличии зарядки и доступности домашней сети стартовал FTPServer, который находился в сети 2 часа, после чего телефон уходил на ребут, что бы обновить медиатеку файлов и сделать доступными плейлисты. Со стороны сервера Ubuntu происходила запись потока с помощью streamrippera, а с помощью крона так же ночью стартовал скрипт, который делал следующее:
- проверял: запущен ли streamripper и нет ли его копий в процессах. Если подобного не находилось, то скрипт организовывал отдельный дочерний процес�� работы streamrippera.
- рассортировывал скачанные треки по папкам в формате 'Название радио — ДД.ММ.ГГГГ'
- в этих же папках создавал плейлист *.m3u согласно времени создания mp3 файла. Т.е. фактически в том порядке, как они звучали на радио.
- для экономии места, удалял старые записи на сервере (лимит прописан в скрипте)
- удалял старые записи на Андроиде (лимит прописан в скрипте)
- добавлял новые на Андроид (лимит прописан в скрипте)
Такая небольшая домашняя автоматизация живет у меня уже пару недель и серъезных багов замечено пока не было. Особенно приятно вставать утром, нацепить наушники и радоваться, что новой музыки вдоволь и весь процесс переноса записей происходит в фоне для меня, как человека.
Собственно, вот такая небольшая история не столько о музыке, а о тех возможностях, которые нам сегодня дают современные разработки для того, что бы сделать жизнь более удобной, оставляя время на творчество, а не на рутину.
Использованные «материалы»:
- FTP сервер для Android FTPServer
- планировщик, обладающий расширенными возможностями для Android Tasker
- Python
- операционная система Ubuntu Server 12.04
- мобильный телефон HTC HD2 с операционной системой Android на борту
P. S.
Пара рекомендаций и замечаний:
- на роутере лучше выполнить привязку по mac-адресу для своего смартфона, что бы сделать постоянную выдачу только одного ip адреса ftp сервера
- периодически проверяйте свободное место на flash-карте телефона
- в среднем одни сутки потока (при 128кбит/с) занимают 0.8-1.2Гб
- скорость передачи от сервера до смартфона по воздуху составляет примерно 2.5-3Мб/С
Ниже предоставлю текст скрипта, в общем там довольно много пояснений, должно быть понятно «на лету».
#!/usr/bin/python # -*- coding: utf-8 -*- #******************Необходимые библиотеки******************* #*********************************************************** import os import shutil from datetime import * import time from operator import itemgetter from ftplib import FTP import ftplib import subprocess import commands #*********************************************************** #******************Необходимые библиотеки******************* #******************Входные параметры************************ #*********************************************************** dir_exec = '' #Исходная папка, в которой будет лунапарк с блэкджеком и шлюхами dir_music = '' #Папка, в которую будет писаться поток инет-радио(она же создается при старте streamripper) dir_music = os.path.join(dir_exec,dir_music) #Абсолютный путь до папки alias_dir = '' #Как будем обзывать папку. Полный формат: 'Название радио - ДД.ММ.ГГГГ' time_shift = 3 #разница в часах между Вами и городом вещания time_sync = 4 #Количество дней, которые синхронизируем с Андроидом time_storage = 30 #Количество дней, которые храним в записи на серваке server = '192.168.1.126' #FTP server, который поднимем на Андроиде login = '' #Логин FTP pass_ftp = ' ' # Пароль FTP port = '2121' #Порт FTP andr_music = '/mnt/sdcard/Music' #Папка музыки на FTP rec_proc = ['streamripper', 'адрес потока радио', '-d', '-D', '%S/%A-%T'] #Параметры запуска streamrippera`а #*********************************************************** #******************Входные параметры************************ #******************Описание функций************************* #*********************************************************** #***Запустить процесс записи радиостанции через streamripper (если он еще не запущен) путем организации дочернего процесса без shell`а def start_rec(proc_stream): num = commands.getoutput('pidof %s |wc -w' % proc_stream) if int(num) >= 2: os.system('killall ' + proc_stream) num = commands.getoutput('pidof %s |wc -w' % proc_stream) if int(num) == 0: proc = subprocess.Popen([rec_proc[0], rec_proc[1], rec_proc[2], dir_exec, rec_proc[3], rec_proc[4],], stdout=subprocess.PIPE) #***Функция проверки в сети или нет ftp-сервер def ftp_online(): ftpConnect = FTP() try: ftpConnect.connect(server,port) ftpConnect.login(login, pass_ftp) ftpConnect.quit() ftpConnect.close() enable = 1 except: enable = 0 return enable #***Функция создания папки, если ее нет. def mdir(create_dir): if not os.path.isdir(create_dir): os.makedirs(create_dir) #***Функция получения уникальных дат из списка всех доступных дат создания треков def uniq_date(lst): return reduce(lambda y,z: not (z in y) and y.append(z) or y, lst, []) #***Определение даты записи mp3 файла def date_mp3(path_dir_music): out_date=[] names = os.listdir(path_dir_music) for name in names: if name.endswith('mp3'): path = os.path.join(path_dir_music,name) mtime = os.path.getmtime(path) - time_shift*60*60 out_date.append(date.fromtimestamp(mtime).strftime('%d.%m.%Y')) return out_date #***Определение имени mp3 файла def name_mp3(path_dir_music): out_name=[] names = os.listdir(path_dir_music) for name in names: if name.endswith('mp3'): out_name.append(name) return out_name #***Функция создания плейлиста. Треки в плейлисте идут в порядке очередности звучания в эфире. def m3u(dir,path): m3u_n_d = [] m3u_n_s = [] m3u_names = name_mp3(path) i=0 for m3u_name in m3u_names: m3u_mtime = os.path.getmtime(os.path.join(path,m3u_name)) - time_shift*60*60 m3u_n_d.append([m3u_name,m3u_mtime]) i=i+1 m3u_n_d = sorted(m3u_n_d, key=itemgetter(1)) w = open(path+'/'+dir+'.m3u','w') w.write('#EXTM3U\n') #EXTM3U поставим заголовок m3U for i in range(i): #w.write('./'+m3u_n_d[i][0]+'\n') #Обычный формат плейлиста w.write(m3u_n_d[i][0]+'\n') #Формат плейлиста для PowerAmp w.close #***Функция получения списка файлов и директорий методом NLST. def ftp_nlst(pwd): log = [] ftpConnect.cwd(pwd) ftpConnect.retrlines('NLST', callback=log.append) return log #***Функция получения списка файлов и директорий методом LIST def ftp_list(pwd): log = [] file_ftp = [] ftpConnect.cwd(pwd) ftpConnect.retrlines('LIST', callback=log.append) files = (line.split(':')[1] for line in log) p = list(files) for name in p: file_ftp.append((name[3:])) return file_ftp #***Функция получения разницы дней между папкой и между сегодняшней датой def num_day(dir_time): date_ = time.mktime(time.strptime(dir_time.split(' - ')[1],'%d.%m.%Y')) now_ = time.time() day_beet = (now_-date_)/(60*60*24) return int(day_beet) #*********************************************************** #******************Описание функций************************* #******************Тело скрипта***************************** #*********************************************************** #Запустим запись start_rec(rec_proc[0]) #Получим список уникальных дат и создадим папки под них creates_dirs = [] u_dates = uniq_date(date_mp3(dir_music)) j=0 for u_date in u_dates: mdir(os.path.join(dir_exec,alias_dir+u_date)) creates_dirs.append([alias_dir+u_date,os.path.join(dir_exec,alias_dir+u_date)]) j=j+1 #Пробежимся по списку mp3 файлов и раскидаем их по разным папкам out_names = name_mp3(dir_music) for out_name in out_names: path = os.path.join(dir_music,out_name) mtime = os.path.getmtime(path) - time_shift*60*60 out_date_temp = date.fromtimestamp(mtime).strftime('%d.%m.%Y') shutil.move(os.path.join(dir_music,out_name),os.path.join(dir_exec,alias_dir+out_date_temp,out_name)) #Запишем в каждой папке свой плейлист for j in range(j): m3u(creates_dirs[j][0],creates_dirs[j][1]) #Удаляем старые папки на сервере dir_rem_serv = os.listdir(dir_exec) for dir_rem_s in dir_rem_serv: if dir_rem_s[:15] == alias_dir: if num_day(dir_rem_s) > time_storage: shutil.rmtree(os.path.join(dir_exec,dir_rem_s)) #Удаляем старые папки на андроиде if ftp_online() == 1: ftpConnect = FTP() ftpConnect.connect(server,port) ftpConnect.login(login, pass_ftp) ftpConnect.cwd(andr_music) #Переходим в папку музыки на Андроиде st = ftpConnect.pwd() #Сохранем это значение в переменную dir_on_root = ftp_list(st) for dir_rem in dir_on_root: if dir_rem[:15] == alias_dir: if num_day(dir_rem) > time_sync: ftpConnect.cwd(os.path.join(st,dir_rem)) files_rem = ftp_list(os.path.join(st,dir_rem)) for file_rem in files_rem: ftpConnect.delete(file_rem) #Удаляем все файлы из старого каталога ftpConnect.cwd(st) ftpConnect.rmd(os.path.join(st,dir_rem)) #Удаляем сам каталог ftpConnect.quit() ftpConnect.close() #Заливаем папки на андроид dir_names = os.listdir(dir_exec) for dir_name in dir_names: if dir_name[:15] == alias_dir: if num_day(dir_name) <= time_sync and ftp_online() == 1: ftpConnect = FTP() ftpConnect.connect(server,port) ftpConnect.login(login, pass_ftp) ftpConnect.cwd(andr_music) #Переходим в папку музыки на Андроиде st = ftpConnect.pwd() #Сохранем это значение в переменную dir_on_root = ftp_list(st) if dir_name not in dir_on_root: ftpConnect.mkd(os.path.join(st,dir_name)) ftpConnect.cwd(os.path.join(st,dir_name)) list_files = os.listdir(os.path.join(dir_exec,dir_name)) files_on_dir = ftp_list(os.path.join(st,dir_name)) for file in list_files: st_file = str(os.path.join(dir_exec,dir_name,file)) if file not in files_on_dir: ftpConnect.storbinary('STOR ' + file, open(st_file, 'rb'),1024) if file.endswith('m3u'): ftpConnect.storbinary('STOR ' + file, open(st_file, 'rb'),1024) ftpConnect.quit() ftpConnect.close() #*********************************************************** #******************Тело скрипта******************************
