Требуемый итоговый функционал
- Возможность назначения своего плейлиста для произвольного временного отрезка
- Поддержка подхвата OGG, MP3, FLAC в качестве источника для аудиопотока
- Динамичность конфигурирования
- Простота редактирования контента для радиоэфира
- Работа под Linux
- Возможность начинающему Linux-пользователю установить и настроить всё это
После довольно долгих проб различных способов получения данного функционала, я остановился именно на Liquidsoap + IceCast. Последний был взят за соответствие требованиям и широкую распространённость (в принципе, аналоги я даже не искал), а Liquidsoap за воистину потрясающие возможности, доступные посредством его функционального языка скриптования. До него я рассматривал ices, ices + ardj, AirTime, что-то ещё, что даже и не упомнить, но все они так или иначе мне не подходили. В общем, я решил воспользоваться Liquidsoap. К сожалению, всей мощи его я не смогу ощутить — вся документация написана на английском, а с ним у меня не всё гладко — однако что-то я усвоил и постараюсь описать всё, чем могу оперировать.
Установка
Не долго думая я решил всё это чудо поднять на своём ноутбуке под openSUSE 12.2 x64, дабы было удобно изучать функционал Liquidsoap, а уже потом перенести на рабочую машину. В репозиториях присутствовал лишь IceCast, Liquidsoap же пришлось собирать. Пользователи Debian/Ubuntu, Windows, Mac OS X, FreeBSD и ArchLinux могут взять готовые пакеты на официальном сайте.
IceCast
Установку IceCast я выполнил из стандартных репозиториев:
# zypper in icecast
Подробно описывать конфигурацию IceCast я не буду, лишь приведу рабочий пример того, что крутится у меня:
/etc/icecast.xml
<icecast> <limits> <clients>100</clients> <sources>2</sources> <threadpool>5</threadpool> <queue-size>524288</queue-size> <client-timeout>30</client-timeout> <header-timeout>15</header-timeout> <source-timeout>10</source-timeout> <burst-on-connect>1</burst-on-connect> <burst-size>65535</burst-size> </limits> <authentication> <source-password>mypass</source-password> <relay-password>mysecondpass</relay-password> <admin-user>adminuser</admin-user> <admin-password>mythirdpass</admin-password> </authentication> <hostname>localhost</hostname> <listen-socket> <port>8000</port> </listen-socket> <fileserve>1</fileserve> #_____________________________________________________________ # Наш запасной источник. В статье о нём не упоминается, ибо и у меня пока что он не сделан так, чтобы людям не стыдно было показать. А вообще к нему цепляется ices с музыкой из папки secure. <mount> <mount-name>/secure</mount-name> <hidden>1</hidden> # Делаем невидимым - пользователи не смогут им воспользоваться <charset>UTF8</charset> # Кодировка </mount> # Описываем соответствующие mount'ы <mount> <fallback-mount>/secure</fallback-mount> # Какой источник подхватывать, если текущий упал <fallback-override>1</fallback-override> # Позволяет перебрасывать текущих клиентов на запасной источник без потери связи <fallback-when-full>1</fallback-when-full> # При достижении максимума слушателей, новому клиенту будет представлен запасной источник <mount-name>/HabraRadio_192</mount-name> # Название mount'а. <charset>UTF8</charset> # Кодировка </mount> <mount> <fallback-mount>/secure</fallback-mount> <fallback-override>1</fallback-override> <fallback-when-full>1</fallback-when-full> <mount-name>/HabraRadio_320</mount-name> <charset>UTF8</charset> </mount> <mount> <fallback-mount>/secure</fallback-mount> <fallback-override>1</fallback-override> <fallback-when-full>1</fallback-when-full> <mount-name>/HabraRadio_vorbis_avg_128</mount-name> <charset>UTF8</charset> </mount> #_____________________________________________________________ <paths> <basedir>/usr/share/icecast</basedir> <logdir>/var/log/icecast</logdir> <webroot>/usr/share/icecast/web</webroot> <adminroot>/usr/share/icecast/admin</adminroot> <alias source="/" dest="/status.xsl"/> </paths> <logging> <accesslog>access.log</accesslog> <errorlog>error.log</errorlog> <loglevel>3</loglevel> <logsize>10000</logsize> </logging> <security> <chroot>0</chroot> <changeowner> <user>icecast</user> <group>icecast</group> </changeowner> </security> </icecast>
Liquidsoap
Качаем, подготавливаем:
$ git clone https://github.com/savonet/liquidsoap-full.git liquidsoap $ cd liquidsoap $ make init $ cp PACKAGES.minimal PACKAGES
Для меня достаточно минимального набора, но если кому нужна поддержка ещё чего-либо, то следует отредактировать файл PACKAGES.
Для нормальной компиляции программы я устанавливал следующие пакеты:
# zypper in make autoconf automake ocaml libao-devel libmad-devel libmp3lame-devel flac-devel libgavl-devel ocaml-camomile-devel ocaml-camlimages-devel ocaml-camomile-data libtheora-devel ocaml-findlib-devel libsamplerate-devel libtag-devel libvorbis-devel gcc-c++ ocaml-pcre-devel libtiff-devel libjpeg62-devel libXpm-devel
Собираем:
$ ./bootstrap $ ./configure --with-user=user --with-group=users $ make # make install
К слову, после выполнения "./configure …" стоит проверить, весь ли функционал, нужный нам, будет доступен в собранной программе. Сделать это можно просто посмотрев на таблицу, выведенную при завершении "./configure …"
Всё, проверить работоспособность программы можно выполнив «liquidsoap —version» — ошибок быть не должно.
Ориентируемся с расписанием эфира
Предположим, что нам необходимо получить приблизительно следующее:
- 02:00-06:00 — ночной плейлист
- 06:00-09:00 — утренний плейлист
- 09:00-19:00 — дневной плейлист
- 19:00-02:00 — вечерний плейлист
- пн, ср, пт — 21:00-22:00 — одна программа
- пн, ср, чт, пт — 18:00-19:00 — вторая программа
Расположение в файловой системе:
radio ├── collection | Аудиофайлы │ ├── efir | Музыка и джинглы │ │ ├── daytime | Дневной плейлист │ │ │ ├── jingles | Джинглы, играющие днём │ │ │ └── music | Музыка, играющая днём │ │ ├── evening | Соответственно, вечер │ │ │ ├── jingles │ │ │ └── music │ │ ├── morning | Утро │ │ │ ├── jingles │ │ │ └── music │ │ └── night | И ночь │ │ ├── jingles │ │ └── music │ ├── programs │ │ ├── 1_prog | 1 программа │ │ ├── 2_prog | 2 программа │ ├── promo | Информационные вставки │ └── security | Папка с запасной музыкой ├── technical | Конфиги, логи └── документация | Описание всего этого
Конфигурация Liquidsoap
Сразу приведу готовую конфигурацию:
./radio/technical/start_liquidsoap
#!/usr/local/bin/liquidsoap # создаём переменные быстрого исправления в одном месте по необходимости # базовая информация о выводимом потоке out = output.icecast( # хост с icecast host = "127.0.0.1", # его порт port = 8000, # логин user = "source", # и пароль password = "mypass", # название name = "Интернет-радио", # жанр genre = "Rock", # ссылка на сайт url = "http://habrahabr.ru", # кодировка encoding = "UTF-8" ) # включаем telnet-сервер set("server.telnet.bind_addr","127.0.0.1") set("server.telnet",true) # _____________________________________ # Описание файловой структуры нашего радиосервера. # Переменные можно не использовать, а писать сразу полные пути к плейлистам, но при изменении названия одной из папок, придётся править довольно много строк в конфигурации. Как показала практика, такой подход удобнее. # абсолютный путь к рабочей директории wd = "/home/user/radio" # путь к папке с аудиофайлами pl = "#{wd}/collection" # техническая папка tech = "#{wd}/technical" # логи set("log.file.path","#{tech}/liquidsoap.log") # путь к файлу лога set("log.level", 3) # уровень логирования # папка с информационными вставками promo_dir = "#{pl}/promo" # папка с программами progr_dir = "#{pl}/programs" # папка с изменяющимся эфиром ef = "#{pl}/efir" # папки соответствующих эфиров ni = "#{ef}/night" mo = "#{ef}/morning" da = "#{ef}/daytime" ev = "#{ef}/evening" # папки с музыкой mus_ni_dir = "#{ni}/music" mus_mo_dir = "#{mo}/music" mus_da_dir = "#{da}/music" mus_ev_dir = "#{ev}/music" # папки с джинглами jin_ni_dir = "#{ni}/jingles" jin_mo_dir = "#{mo}/jingles" jin_da_dir = "#{da}/jingles" jin_ev_dir = "#{ev}/jingles" # плейлисты с программами. Обратите внимание - до этого указывались пути к папкам, а здесь - к простым текстовым файлам. 1_prog_pl = "#{progr_dir}/1_prog.pl" 2_prog_pl = "#{progr_dir}/2_prog.pl" # _____________________________________ # Создаём объекты типа "source", в нашем случае это аудиоисточники. # Здесь атрибут "reload" позволяет раз в 360 секунд перечитывать плейлист по пути, указанному далее. # По умолчанию, музыка проигрывается рандомно, атрибут <code>mode = "normal"</code> указывает на проигрывание по порядку. # загружаем плейлисты, джинглы, вставки, программы mus_ni = playlist (reload = 360, "#{mus_ni_dir}") mus_mo = playlist (reload = 360, "#{mus_mo_dir}") mus_da = playlist (reload = 360, "#{mus_da_dir}") mus_ev = playlist (reload = 360, "#{mus_ev_dir}") jin_ni = playlist (reload = 360, "#{jin_ni_dir}") jin_mo = playlist (reload = 360, "#{jin_mo_dir}") jin_da = playlist (reload = 360, "#{jin_da_dir}") jin_ev = playlist (reload = 360, "#{jin_ev_dir}") promo = playlist (reload = 360, "#{promo_dir}") 1_prog = playlist (reload = 360, "#{1_prog_pl}", mode = "normal") 2_prog = playlist (reload = 360, "#{2_prog_pl}", mode = "normal") # _____________________________________ # строим 4 потока, сразу всё перемешивая # смешиваем вставки ins_ni = rotate (weights = [2, 1], [jin_ni, promo]) ins_mo = rotate (weights = [2, 1], [jin_mo, promo]) ins_da = rotate (weights = [2, 1], [jin_da, promo]) ins_ev = rotate (weights = [2, 1], [jin_ev, promo]) # смешиваем вставки и потоки ni = rotate (weights = [3, 1], [mus_ni, ins_ni]) mo = rotate (weights = [3, 1], [mus_mo, ins_mo]) da = rotate (weights = [3, 1], [mus_da, ins_da]) ev = rotate (weights = [3, 1], [mus_ev, ins_ev]) #_______________________________________________________________________ # конфигурируем расписание эфира radio = switch (track_sensitive = true, [ ({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog), ({ (1w18h - 1w19h) or (3w18h - 3w19h) or (4w18h - 4w19h) or (5w18h - 5w19h)}, 2_prog), ({ 2h - 6h }, ni), ({ 6h - 9h }, mo), ({ 9h - 19h }, da), ({ 19h - 2h }, ev) ]) #_______________________________________________________________________ # добавляем crossfade radio = crossfade(start_next=1., fade_out=1., fade_in=1., radio) # и, наконец, запускаем вещалки с разным качеством out( %vorbis.abr(samplerate = 44100, channels = 2, bitrate = 128, max_bitrate = 192, min_bitrate = 96), description = "Average vorbis 96-128-192 Kbps", mount = "HabraRadio_vorbis_avg_128", mksafe(radio) ) out( %mp3(bitrate = 320, id3v2 = true), description = "MP3 320 Kbps", mount = "HabraRadio_320", mksafe(radio) ) out( %mp3(bitrate = 192, id3v2 = true), description = "MP3 192 Kbps", mount = "HabraRadio_192", mksafe(radio) )
Несколько комментариев:
1) Эти строки:
wd = "/home/user/radio" pl = "#{wd}/collection" ef = "#{pl}/efir" ni = "#{ef}/night" mus_ni_dir = "#{ni}/music" mus_ni = playlist (reload = 360, "#{mus_ni_dir}")
вполне успешно можно заменить на
mus_ni = playlist (reload = 360, "/home/user/radio/collection/efir/night/music")
и ничего вам за это не будет — Liquidsoap просто вместо #{wd} подставляет значение переменной wd.
2) В месте, где мы внедряли вставки, были строки:
ins_ni = rotate (weights = [2, 1], [jin_ni, promo]) ni = rotate (weights = [3, 1], [mus_ni, ins_ni])
rotate()
— позволяет регулировать очередью
weights = [2, 1], [jin_ni, promo]
— указывает брать 2 трека из jin_ni, затем 1 из promo, после повторно 2 трека из jin_ni и так далее.
weights = [3, 1], [mus_ni, ins_ni]
— указывает брать 3 трека из mus_ni, затем 1 трек из того уже перемешанного плейлиста, что получился строкой ранее (ins_ni).
3) При конфигурировании расписания эфира мы использовали следующие строки:
radio = switch (track_sensitive = true, [ ({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog), ({ (1w18h - 1w19h) or (3w18h - 3w19h) or (4w18h - 4w19h) or (5w18h - 5w19h)}, 2_prog), ({ 2h - 6h }, ni), ({ 6h - 9h }, mo), ({ 9h - 19h }, da), ({ 19h - 2h }, ev) ])
switch()
— переключает аудиоисточники в заданное время.
track_sensitive = true
— позволяет не прерывать текущий трек, даже если время активного плейлиста истекло. Т.е. если ночной трек начался в 05:59, то, пока он не закончится, в силу не вступит утренний плейлист.
({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog),
— по понедельникам, средам и пятницам с 21 до 22 часов играть источник 1_prog.
Насколько я понял, те строкив списке switch(), что выше расположены, имеют высший приоритет.
Формирование плейлиста радиопрограммы
Принцип прост: в папке prog_1 лежат файлы радиопередачи вида «01_ProgName», где записаны голоса радиоведущих. Плейлист будет вида:
01_ProgName
Music152
02_ProgName
Music241
03_ProgName
Music937
…
Думаю, на bash было бы правильней писать генератор такого плейлиста, но я недавно дорвался до Python, посему на скорую руку написал на нём:
./radio/technical/generatorProg1.py
#!/usr/bin/env python2 # -*- coding: utf-8 -*- import os import random finalPaylist = '/home/user/radio/collection/programs/1_prog.pl' music = '/home/user/radio/collection/efir/evening/music/' show = '/home/user/radio/collection/programs/1_prog/' myShow = sorted(os.listdir(show)) myMusic = os.listdir(music) listOfTracks = [] def getRandomTrack(list): i = 0 buf = random.choice(myMusic) while (buf in list) & (not i == 100): i += 1 buf = random.choice(myMusic) return buf for i in range(60): if not i%2: try: listOfTracks.append(show + myShow[i/2]) except : listOfTracks.append(music + getRandomTrack(listOfTracks)) else: listOfTracks.append(music + getRandomTrack(listOfTracks)) myFile = open(finalPaylist, 'w') for i in range(len(listOfTracks)): myFile.write(listOfTracks[i]+'\n')
В итоге получается плейлист, который покрывает как минимум час эфира даже без файлов радиопередачи. Главное не забыть подхватить этот плейлист в нерандомном режиме. Ну и для второй программы так же плейлист стоит генерировать.
Финальные штрихи
Заносим в crontab генерирование плейлистов:
crontab -e
0 19 * * * /home/user/radio/technical/generatorProg1.py 0 16 * * * /home/user/radio/technical/generatorProg2.py
Добавляем в автозапуск KDE пару строк:
/home/user/.kde4/Autostart/start_liquidsoap.sh
#!/bin/sh cp /home/user/radio/technical/liquidsoap.log /home/user/radio/technical/liquidsoap_backup.log cat /dev/null > /home/user/radio/technical/liquidsoap.log liquidsoap /home/user/radio/technical/start_liquidsoap
Автозапустить IceCast тоже не помешало бы
/etc/init.d/icecast start chkconfig --add icecast
И, что важно, на последок стоит настроить NTP — здесь довольно много зависит от верно установленного времени. Его я настроил из-под YaST.
В заключение
Сейчас это радио успешно работает, файлы добавляются ответственным человеком удалённо из-под Windows, что стало возможным с помощью связки openVPN+Samba, программы играют, логи пишутся. На будущее задумано ещё пара фич, среди которых: проигрывание различных вставок в начале каждого часа, реализация своей рандомизации плейлистов, удалённые конференц-звонки в прямой эфир, причёсывание всего и вся, настройка пущей отказоустойчивости, а так же поиск подводных камней. Вообще разбираться с Liquidsoap — одно удовольствие. Хорошей всем музыки.
ссылка на оригинал статьи http://habrahabr.ru/post/156591/
Добавить комментарий