📜 ⬆️ ⬇️

Solving the problem of storing music for Internet radio


Good afternoon,% username%!
Somehow we decided to make an Internet radio with a company of friends, but as it turned out, the allocated space on the VPS is not enough for a large archive of music, moreover buying additional gigabytes is a real robbery.

I was looking for a solution for a long time, when suddenly I came across an excellent article by ableev “Yandex.Disk as a file system” . I had an idea, why not store music on the Yandex disk? Here we omit the issue of licensing and copyright - this is a completely different story, I am interested in the technical part. As it turned out, IceCast does not always manage to load music from the Yandex disk, which leads to stumbles and interruptions in broadcasting, and this is not good at all. This problem caught me, and I found a solution - to determine what was playing on the radio at the moment and load the next tracks in advance, and remove the lost tracks from the server. This generates traffic, I agree, but at the moment VPS with unlimited traffic is full, but there is no unlimited space on the disks.

Since of the languages ​​I somehow own C #, I had to resort to mono, and also write a few auxiliary scripts in Python, PHP, bash.

Auxiliary scripts in the studio!
')
id3.py gets, when called by the argument, a track from which it takes tags and writes them into a text file:
id3.py
#!/usr/bin/env python class Unbuffered(object): def __init__(self, stream): self.stream = stream def write(self, data): self.stream.write(data) self.stream.flush() def __getattr__(self, attr): return getattr(self.stream, attr) import eyeD3 import sys import argparse def createParser (): parser = argparse.ArgumentParser() parser.add_argument('-f', '--file') return parser if __name__ == '__main__': parser = createParser() namespace = parser.parse_args(sys.argv[1:]) tag = eyeD3.Tag() tag.link(namespace.file) sys.stdout = Unbuffered(sys.stdout) s = tag.getArtist()+" - "+tag.getTitle() f = open('tag.txt','w') f.write(s) f.close() 



getCurrent.php parses the IceCast page with information about the current track and gives the name of the playing track.
getCurrent.php
 <?php error_reporting(0); header("Content-Type: text/html; charset=UTF-8"); $file_name="http://localhost:8000/status.xsl?mount=/stream"; $r=fopen($file_name,'r'); $text=fread($r,10000); fclose($r); $mas=explode('<tr>', $text); $name = explode(':', $mas[3]); $q = explode ('</td>',$name[1]); $q2 = explode ('<td class="streamdata">',$q[1]); $rj = $q2[1]; if($rj == "0" or $rj == ""){ echo " Nonstop"; }else { $fl = file_get_contents('http://localhost:8000/status.xsl?mount=/stream'); function antara($string, $start, $end){ $string = " ".$string; $ini = strpos($string,$start); if ($ini == 0) return ""; $ini += strlen($start); $len = strpos($string,$end,$ini) - $ini; return substr($string,$ini,$len); } $stream = antara($fl,"<td>Stream Title:</td>\n<td class=\"streamdata\">","</td>"); $description = antara($fl, "<td>Stream Description:</td>\n<td class=\"streamdata\">", "</td>"); $listeners = antara($fl, "<td>Current Listeners:</td>\n<td class=\"streamdata\">", "</td>"); $max = antara($fl, "<td>Peak Listeners:</td>\n<td class=\"streamdata\">", "</td>"); $song = antara($fl, "<td>Current Song:</td>\n<td class=\"streamdata\">", "</td>"); echo $song; ?> 



A small script on bash isOnline.sh checks if the radio script is running and the status information is also written to a text file.
isOnline.sh
 #!/bin/bash rm isOnline.txt ps ax | grep -v grep | grep radio.sh > isOnline.txt 



The radio script liquidsoap radio.sh is the radio itself.
radio.sh
 #!/usr/bin/liquidsoap #          #      out = output.icecast( #   icecast host = "127.0.0.1", #   port = 8000, #  user = "source", #   password = "password", #  name = "Radio Name", #  genre = "Various", #    url = "http://www.host.local" #  encoding = "UTF-8" ) #  telnet- #set("server.telnet.bind_addr","127.0.0.1") #set("server.telnet",true) # _____________________________________ #     . #    ,       ,       ,       .   ,   . #      #wd = "/home/admin/radio" #      #pl = "#{wd}/collection" #   #tech = "#{wd}/technical" #  set("log.file.append",true) set("log.file",true) set("log.file.path","/var/data/liquidsoap.log") #     set("log.level", 3) #   #set("buffering.kind","disk_manyfiles") #set("decoding.buffer_length",30.) #set("buffering.path","/tmp/radio") #     #promo_dir = "#{pl}/promo" #    #progr_dir = "#{pl}/programs" #     #ef = "#{pl}/efir" #    #ni = "#{ef}/night" #    mus_ni_dir = "loc.playlist.txt" #    jin_ni_dir = "/mnt/username.yadisk/RT/jingles" #promo promo_dir = "/mnt/username.yadisk/RT/promo" mus_ni = playlist (reload = 86400, "#{mus_ni_dir}", mode = "normal") jin_ni = playlist (reload = 86400, "#{jin_ni_dir}", mode = "normal") promo = playlist (reload = 86400, "#{promo_dir}") ni = rotate (weights = [1, 10, 1, 5], [jin_ni, mus_ni, promo, mus_ni]) radio = switch (track_sensitive = true, [ #({ (2w16h10m - 2w16h20m) or (3w14h - 3w14h5m) or (4w16h - 4w16h5m)}, spekt), ({ (3w23h - 3w23h5m) or (4w10h - 4w10h5m)}, pozdr), ({ (2w18h - 2w18h5m) or #(3w18h - 3w18h5m)}, xmas_trad), ({ (6w14h - 6w14h10m) or (6w18h - 6w18h10m)}, prog1), ({ (6w0h - 7w18h)}, xmas_mus_prog), ({ 1w0h - 7w23h59m }, ni) ]) radio = mksafe(radio) radio = crossfade(start_next=2., fade_out=2., fade_in=2., radio) out( %mp3(bitrate = 128, id3v2 = true), description = "Radio Name 128kbps", mount = "stream", mksafe(radio) ) 



The generator of the playlist, generator.sh, creates a list of files on a mounted disk, shuffles the list and writes to a text file. This script is remarkable because here you can add a lot of discs and collect everything in 1 playlist.
generator.sh
 #!/bin/sh find "/mnt/username.yadisk/RT/music" -name '*.mp3' -print > "disk.playlist.txt" shuf -n 500 "disk.playlist.txt" -o "disk.playlist.txt" sed 's/\/mnt\/username.yadisk\/RT\/music/\/home\/admin\/rt\/tmp/g' disk.playlist.txt > loc.playlist.txt 



It now remains to write a control script that will monitor whether the radio script has fallen, launch it in the event of a fall, monitor the playlist, load and delete tracks.
radio_control.cs
 using System; using System.Collections.Generic; using System.IO; using System.Collections; using System.Diagnostics; namespace radio_control { class song { string filename; string tagname; string prepared; System.Diagnostics.Process getTag; public song() { getTag = new System.Diagnostics.Process(); getTag.EnableRaisingEvents = false; getTag.StartInfo.FileName = "./id3.py"; } public string FileName { get { return filename; } set { filename = value; } } public string Tag { get { return tagname; } set { tagname = value; } } public string Prepared { get { return prepared; } set { prepared = value; } } /// <summary> ///     ,        Prepared /// </summary> /// <param name="tmpDir"></param> public void Prepare(string tmpDir) { File.Copy(FileName, tmpDir+Path.GetFileName(FileName), true); Prepared = tmpDir + Path.GetFileName(FileName); Tag = _getTag().Replace(Environment.NewLine,""); } //     public void Destroy() { File.Delete(Prepared); } //    string _getTag() { getTag.StartInfo.Arguments = "-f " + this.Prepared; getTag.Start(); getTag.WaitForExit(); StreamReader str = new StreamReader("tag.txt"); string tag = str.ReadToEnd(); str.Close(); return tag; } } class Program { static void Main(string[] args) { //,       int count = 0; string pl_file = "/home/admin/rt/disk.playlist.txt"; //  string tmpDir = "/home/admin/rt/tmp/"; //     string newplTime = "23:30"; //    List<song> songs = new List<song>(); //      icecast System.Diagnostics.Process getCurrent = new System.Diagnostics.Process(); getCurrent.EnableRaisingEvents = false; getCurrent.StartInfo.RedirectStandardOutput = true; getCurrent.StartInfo.FileName = "/usr/bin/php"; getCurrent.StartInfo.UseShellExecute = false; getCurrent.StartInfo.Arguments = "getCurrent.php"; // ,     System.Diagnostics.Process isRadio = new System.Diagnostics.Process(); isRadio.EnableRaisingEvents = false; getCurrent.StartInfo.UseShellExecute = false; getCurrent.StartInfo.RedirectStandardOutput = true; isRadio.StartInfo.FileName = "isOnline.sh"; //   System.Diagnostics.Process radio = new System.Diagnostics.Process(); radio.EnableRaisingEvents = false; getCurrent.StartInfo.UseShellExecute = false; getCurrent.StartInfo.RedirectStandardOutput = true; radio.StartInfo.FileName = "screen"; radio.StartInfo.Arguments = "-dmS radio liquidsoap --verbose radiotera.sh"; //   radio System.Diagnostics.Process killRadio = new System.Diagnostics.Process(); killRadio.EnableRaisingEvents = false; getCurrent.StartInfo.UseShellExecute = false; getCurrent.StartInfo.RedirectStandardOutput = true; killRadio.StartInfo.FileName = "screen"; killRadio.StartInfo.Arguments = "-X -S radio quit"; //    Process genPl = new Process(); genPl.EnableRaisingEvents = false; genPl.StartInfo.UseShellExecute = false; genPl.StartInfo.FileName = "generator.sh"; //   ,  -    first in - first out Queue queue = new Queue(3); //     string[] playlist = (System.IO.File.ReadAllLines(pl_file)); log("Loading playlist"); // ID3       song sng; foreach (string value in playlist) { sng = new song(); sng.FileName = value; songs.Add(sng); } log("Playlist loaded.", ConsoleColor.Green); //  3     songs[0].Prepare(tmpDir); count++; songs[1].Prepare(tmpDir); count++; songs[2].Prepare(tmpDir); count++; //    queue.Enqueue(songs[0]); queue.Enqueue(songs[1]); queue.Enqueue(songs[2]); log("First 3 tracks prepared:\n"+songs[0].Tag+"\n"+songs[1].Tag+"\n"+songs[2].Tag, ConsoleColor.Green); //   song tmp = new song(); StreamReader rdr; string online = ""; while (true) { log("Check time to change playlist"); //    if (DateTime.Now.ToString("HH:mm") == newplTime) { log("Its time to change playlist!", ConsoleColor.Red); genPl.Start(); //  genPl.WaitForExit(); log("Playlist generated", ConsoleColor.Green); log("Loading playlist"); playlist = (System.IO.File.ReadAllLines(pl_file)); songs.Clear(); foreach (string value in playlist) //  { sng = new song(); sng.FileName = value; songs.Add(sng); } log("Playlist loaded", ConsoleColor.Green); } log("Check the availability of radio"); isRadio.Start(); isRadio.WaitForExit(); rdr = new StreamReader("isOnline.txt"); online=rdr.ReadToEnd(); rdr.Close(); //  ? if (online == "") { log("Radio is offline!\nClearing screen", ConsoleColor.Red); killRadio.Start(); killRadio.WaitForExit(); log("Starting the radio"); radio.Start(); log("Waiting for 10 seconds"); System.Threading.Thread.Sleep(10000); } log("Get current song"); //   getCurrent.Start(); getCurrent.WaitForExit(); string curr = getCurrent.StandardOutput.ReadToEnd(); if (curr == "") continue; //    tmp = new song(); tmp = (song)queue.Peek(); //,      log("Now playing: " + curr, ConsoleColor.Cyan); log("Checking queue tag: "+tmp.Tag, ConsoleColor.Cyan); if (tmp.Tag != curr) { //   ,    —   . //           if ((curr == "Radio TERA - Radio TERA") || (curr=="Unknown") || (curr=="NonstopS")) { log("Now playing radio jingle, move on to the next iteration"); continue; } log("The current track is different from the queue!!!\nMove the queue", ConsoleColor.Red); //     ,      queue.Dequeue(); log("Dequene track with tag "+tmp.Tag); //     log("Remove the track ended"); tmp.Destroy(); //      log("Prepare next track"); songs[count].Prepare(tmpDir); //    log("Adding it to queue "+songs[count].Tag); queue.Enqueue(songs[count]); //  count++; log("Count now: " + count.ToString()); //    ,         if (count > songs.Count - 1) { count = 0; } } // 30  System.Threading.Thread.Sleep(30000); } } static void log(string str) { Console.WriteLine(str); } static void log(string str, ConsoleColor frcolor) { Console.ForegroundColor = frcolor; Console.WriteLine(str); Console.ForegroundColor=ConsoleColor.White; } } } 



I apologize for my code, all this could be written within the same script, but I wanted to solve the problem faster and used the tools I owned for various subtasks.

For normal work you will need:


The system is started via radio_control.cs. Everything. Then he will launch the radio himself, generate a playlist, upload the music and at the same time write to the terminal what he is doing.

Unfortunately, we closed the radio, but I really wanted my work not to be in vain, I hope someone helped.

Source: https://habr.com/ru/post/270415/


All Articles