8 from django.template import loader
9 from django.utils.timezone import now
10 import xml.etree.ElementTree as ET
12 from emissions.models import Diffusion, Schedule
13 from .models import SomaLogLine, ScheduledDiffusion, Jingle, RecurringStreamOccurence, Track
14 from .app_settings import app_settings
17 class SomaException(Exception):
21 def get_current_nonstop_track():
23 soma_log_line = SomaLogLine.objects.select_related().order_by('-play_timestamp')[0]
27 if soma_log_line.play_timestamp < (datetime.datetime.now() - datetime.timedelta(hours=1)):
28 # last known line is way too old
30 if not soma_log_line.on_air:
31 # nonstop should be on air but it's not :/
34 current_nonstop_file = soma_log_line.filepath
35 if current_nonstop_file:
36 if 'Tranches/' not in current_nonstop_file.filepath and (
37 'tracks/' not in current_nonstop_file.filepath):
38 # nonstop is playing but it's not a nonstop track :/
40 current_track = soma_log_line.get_track()
41 if current_track is None:
42 # nonstop is playing a nonstop track, but it's unknown :/
44 d = {'track_title': current_track.title}
45 if current_track.artist:
46 d['track_artist'] = current_track.artist.name
50 def get_diffusion_file_path(diffusion):
51 return u'diffusions-auto/%s--%s' % (
52 diffusion.datetime.strftime('%Y%m%d-%H%M'),
53 diffusion.episode.emission.slug)
56 def is_already_in_soma(diffusion):
57 if isinstance(diffusion, Diffusion):
58 if ScheduledDiffusion.objects.filter(diffusion=diffusion).exists():
60 elif isinstance(diffusion, Schedule):
61 if RecurringStreamOccurence.objects.filter(diffusion__schedule=diffusion).exists():
66 def soma_connection():
67 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
68 s.connect(('soma', 12521))
69 s.recv(1024) # -> b'soma 2.5 NO_SSL\n'
70 s.sendall(b'100 - Ok\n')
71 s.recv(1024) # -> b'100 - Welcome to soma daemon\n'
72 s.sendall(b'\n') # (empty password)
73 if s.recv(1024) != b'100 - Ok\n':
74 raise SomaException('failed to initialize soma connection')
78 def add_soma_diffusion(diffusion):
80 context['diffusion'] = diffusion
81 context['jingle'] = diffusion.jingle
82 context['episode'] = diffusion.episode
83 context['start'] = diffusion.datetime
84 context['end'] = diffusion.end_datetime
86 if not diffusion.is_stream():
88 soundfile = diffusion.episode.soundfile_set.filter(fragment=False)[0]
89 diffusion_path = get_diffusion_file_path(diffusion)
92 if not os.path.exists(app_settings.LOCAL_BASE_PATH):
93 raise SomaException('soma directory is not available')
94 local_diffusion_path = os.path.join(app_settings.LOCAL_BASE_PATH, diffusion_path)
95 if os.path.exists(local_diffusion_path):
96 for filename in os.listdir(local_diffusion_path):
97 os.unlink(os.path.join(local_diffusion_path, filename))
99 os.mkdir(os.path.join(app_settings.LOCAL_BASE_PATH, diffusion_path))
101 shutil.copyfile(soundfile.file.path,
102 os.path.join(app_settings.LOCAL_BASE_PATH, diffusion_path, os.path.basename(soundfile.file.path)))
105 os.rmdir(os.path.join(app_settings.LOCAL_BASE_PATH, diffusion_path))
108 raise SomaException('error copying file to soma')
110 context['diffusion_path'] = diffusion_path
111 # end should be a bit before the real end of file so the same file doesn't
112 # get repeated but shouldn't be less or equal than start date or soma would
114 context['end'] = diffusion.datetime + datetime.timedelta(seconds=
115 max(((soundfile.duration or 480) - 180), 60))
118 palinsesti_template = loader.get_template('nonstop/soma_palinsesti.xml')
120 palinsesti = palinsesti_template.render(context)
121 palinsesti_xml = ET.fromstring(palinsesti.encode('utf-8'))
123 palinsesto_xml = get_palinsesto_xml()
124 palinsesto_xml.append(palinsesti_xml)
125 send_palinsesto_xml(palinsesto_xml)
126 diffusion.added_to_nonstop_timestamp = now()
130 def remove_soma_diffusion(diffusion):
131 palinsesto_xml = get_palinsesto_xml()
132 for palinsesto in palinsesto_xml.findall('Palinsesto'):
133 if palinsesto.findall('Description')[0].text.startswith(diffusion.soma_id):
134 palinsesto_xml.remove(palinsesto)
135 send_palinsesto_xml(palinsesto_xml)
138 def send_palinsesto_xml(palinsesto_xml):
139 with soma_connection() as s:
140 s.sendall(b'106 - Switch to a New Palinsesto Request\n')
141 if s.recv(1024) != b'100 - Ok\n':
142 raise SomaException('failed to switch palinsesto')
143 s.sendall(ET.tostring(palinsesto_xml))
145 # give it some time (...)
147 with soma_connection() as s:
148 s.sendall(b'122 - Set the current Palinsesto as Default\n')
149 if s.recv(1024) != b'100 - Ok\n':
150 raise SomaException('failed to set current palinsesto as default')
153 def get_palinsesto_xml():
154 with soma_connection() as s:
155 s.sendall(b'109 - Get the current palinsesto\n')
156 palinsesto_bytes = b''
158 new_bytes = s.recv(200000)
161 palinsesto_bytes += new_bytes
162 if not palinsesto_bytes.startswith(b'100 - Ok\n'):
163 raise SomaException('failed to get palinsesto')
164 palinsesto_bytes = palinsesto_bytes[9:]
165 palinsesto_xml = ET.fromstring(palinsesto_bytes)
166 return palinsesto_xml
170 def __init__(self, zone_settings, zone_ids, recent_tracks_id=None, filter_kwargs={}, k=30):
171 self.zone_settings = zone_settings
172 self.zone_ids = zone_ids
174 self.recent_tracks_id = recent_tracks_id or []
175 self.filter_kwargs = filter_kwargs
178 def append(self, track):
180 self.playlist.append(track)
183 return self.playlist.pop() if self.playlist else None
185 def get_recent_track_ids(self):
186 return self.recent_tracks_id + [x.id for x in self.playlist if isinstance(x, Track)]
188 def get_duration(self):
189 return sum([x.duration for x in self.playlist], datetime.timedelta(seconds=0))
191 def get_random_tracks(self, k=30):
192 weights = self.zone_settings.weights
195 # pick tracks from db
196 tracks = Track.objects.filter(
197 nonstop_zones__in=self.zone_ids,
198 duration__isnull=False,
199 **self.filter_kwargs).exclude(
200 id__in=self.get_recent_track_ids()
201 ).order_by('?')[:k*10]
203 self.recent_tracks_id = self.recent_tracks_id[:len(self.recent_tracks_id) // 2]
206 def compute_weight(track):
208 for weight_key, weight_value in weights.items():
209 if track.match_criteria(weight_key):
210 weight += weight_value
212 weight = 1 + (weight / 20)
214 weight = 1 + (weight / 2)
217 track_weights = [compute_weight(x) for x in tracks]
218 tracks = random.choices(tracks, weights=track_weights, k=k)