Retarder un flux Shoutcast de 12 heures (Linux / bash)
-
19-08-2019 - |
Question
Je vis de chez moi à l'autre bout du monde (à l'heure GMT + 1, GMT + 13 est chez nous) et mon ancienne station de radio terrestre me manque. Il comporte un flux Shoutcast, et je voudrais simplement le retarder de 12 heures pour qu'il soit toujours disponible lorsque je souhaite l'écouter, de manière à ce que son fuseau horaire soit synchronisé avec mon fuseau horaire.
Je vois ceci comme un script exécuté sur l'hôte de mon serveur.
Une approche naïve consisterait simplement à allouer suffisamment de RAM dans un tampon annulaire pour stocker la totalité du délai de 12 heures et à canaliser la sortie de streamripper. Mais le flux est un fichier mp3 de 128 kbps, ce qui voudrait dire (128/8) * 60 * 60 = ~ 56 Mo par heure, ou 675 Mo pour la mémoire tampon de 12 heures, ce qui n’est pas vraiment pratique. De plus, je pourrais avoir à traiter avec mon serveur hôte juste en tuant le processus après un certain délai.
Alors, quelles sont les stratégies qui pourraient être réellement pratiques?
La solution
Un ripper de flux serait le moyen le plus simple, et probablement le bon, mais si vous voulez le faire de la manière du programmeur ....
- La plupart des machines de développement ont un peu de RAM. Êtes-vous sûr de ne pas pouvoir épargner 675 Mo?
- Plutôt que de stocker la sortie dans une mémoire tampon, ne pouvez-vous pas la stocker dans un ou plusieurs fichiers, disons une heure à la fois? (essentiellement, vous écririez votre propre ripper de flux)
- Convertissez le flux en un débit plus faible, si vous pouvez tolérer la perte de qualité
Autres conseils
Pourquoi ne le téléchargez-vous pas simplement avec un extracteur de flux tel que Ripshout ou quelque chose du genre?
Pour répondre à ma propre question, voici un script qui démarre sous forme de tâche cron toutes les 30 minutes. il vide le flux entrant par morceaux de 5 minutes (ou défini par FILE _ SECONDS) dans un répertoire particulier. Les bordures de bloc sont synchronisées sur l'horloge et l'écriture ne commence pas avant la fin du bloc d'heure en cours, de sorte que les tâches cron en cours peuvent se chevaucher sans doubler les données ni laisser d'espaces vides. les fichiers sont nommés comme (heure de la période% nombre de secondes sur 24 heures) .str.
Je n'ai pas encore créé de lecteur, mais l'objectif était de définir le répertoire de sortie sur un emplacement accessible sur le Web et d'écrire un script à exécuter localement, qui utilise le même code de calcul d'horodatage que celui utilisé ici pour un accès séquentiel (horodatage). Il y a 12 heures) .str, rassemblez-les à nouveau, puis configurez-les localement en tant que serveur shoutcast. alors je pourrais simplement pointer mon lecteur de musique sur http: // localhost: port et l'obtenir.
edit: Nouvelle version avec dépassement de délai et meilleure vérification des conditions d'erreur, ainsi qu'un fichier de journal sympa. cela fonctionne actuellement sans accroc sur mon hébergeur partagé (pas cher), sans aucun problème.
#!/usr/bin/python
import time
import urllib
import datetime
import os
import socket
# number of seconds for each file
FILE_SECONDS = 300
# run for 30 minutes
RUN_TIME = 60*30
# size in bytes of each read block
# 16384 = 1 second
BLOCK_SIZE = 16384
MAX_TIMEOUTS = 10
# where to save the files
OUTPUT_DIRECTORY = "dir/"
# URL for original stream
URL = "http://url/path:port"
debug = True
log = None
socket.setdefaulttimeout(10)
class DatestampedWriter:
# output_path MUST have trailing '/'
def __init__(self, output_path, run_seconds ):
self.path = output_path
self.file = None
# needs to be -1 to avoid issue when 0 is a real timestamp
self.curr_timestamp = -1
self.running = False
# don't start until the _end_ of the current time block
# so calculate an initial timestamp as (now+FILE_SECONDS)
self.initial_timestamp = self.CalcTimestamp( FILE_SECONDS )
self.final_timestamp = self.CalcTimestamp( run_seconds )
if debug:
log = open(OUTPUT_DIRECTORY+"log_"+str(self.initial_timestamp)+".txt","w")
log.write("initial timestamp "+str(self.initial_timestamp)+", final "+str(self.final_timestamp)+" (diff "+str(self.final_timestamp-self.initial_timestamp)+")\n")
self.log = log
def Shutdown(self):
if self.file != None:
self.file.close()
# write out buf
# returns True when we should stop
def Write(self, buf):
# check that we have the correct file open
# get timestamp
timestamp = self.CalcTimestamp()
if not self.running :
# should we start?
if timestamp == self.initial_timestamp:
if debug:
self.log.write( "starting running now\n" )
self.log.flush()
self.running = True
# should we open a new file?
if self.running and timestamp != self.curr_timestamp:
if debug:
self.log.write( "new timestamp "+str(timestamp)+"\n" )
self.log.flush()
# close old file
if ( self.file != None ):
self.file.close()
# time to stop?
if ( self.curr_timestamp == self.final_timestamp ):
if debug:
self.log.write( " -- time to stop\n" )
self.log.flush()
self.running = False
return True
# open new file
filename = self.path+str(timestamp)+".str"
#if not os.path.exists(filename):
self.file = open(filename, "w")
self.curr_timestamp = int(timestamp)
#else:
# uh-oh
# if debug:
# self.log.write(" tried to open but failed, already there\n")
# self.running = False
# now write bytes
if self.running:
#print("writing "+str(len(buf)))
self.file.write( buf )
return False
def CalcTimestamp(self, seconds_offset=0):
t = datetime.datetime.now()
seconds = time.mktime(t.timetuple())+seconds_offset
# FILE_SECONDS intervals, 24 hour days
timestamp = seconds - ( seconds % FILE_SECONDS )
timestamp = timestamp % 86400
return int(timestamp)
writer = DatestampedWriter(OUTPUT_DIRECTORY, RUN_TIME)
writer_finished = False
# while been running for < (RUN_TIME + 5 minutes)
now = time.mktime(datetime.datetime.now().timetuple())
stop_time = now + RUN_TIME + 5*60
while not writer_finished and time.mktime(datetime.datetime.now().timetuple())<stop_time:
now = time.mktime(datetime.datetime.now().timetuple())
# open the stream
if debug:
writer.log.write("opening stream... "+str(now)+"/"+str(stop_time)+"\n")
writer.log.flush()
try:
u = urllib.urlopen(URL)
except socket.timeout:
if debug:
writer.log.write("timed out, sleeping 60 seconds\n")
writer.log.flush()
time.sleep(60)
continue
except IOError:
if debug:
writer.log.write("IOError, sleeping 60 seconds\n")
writer.log.flush()
time.sleep(60)
continue
# read 1 block of input
buf = u.read(BLOCK_SIZE)
timeouts = 0
while len(buf) > 0 and not writer_finished and now<stop_time and timeouts<MAX_TIMEOUTS:
# write to disc
writer_finished = writer.Write(buf)
# read 1 block of input
try:
buf = u.read(BLOCK_SIZE)
except socket.timeout:
# catch exception but do nothing about it
if debug:
writer.log.write("read timed out ("+str(timeouts)+")\n")
writer.log.flush()
timeouts = timeouts+1
now = time.mktime(datetime.datetime.now().timetuple())
# stream has closed,
if debug:
writer.log.write("read loop bailed out: timeouts "+str(timeouts)+", time "+str(now)+"\n")
writer.log.flush()
u.close();
# sleep 1 second before trying to open the stream again
time.sleep(1)
now = time.mktime(datetime.datetime.now().timetuple())
writer.Shutdown()