Retrasar una transmisión Shoutcast por 12 horas (Linux / bash)
-
19-08-2019 - |
Pregunta
Estoy viviendo al otro lado del mundo desde mi casa (GMT + 1 ahora, GMT + 13 es mi hogar), y extraño mi vieja estación de radio terrestre. Tiene una transmisión Shoutcast, y me gustaría simplemente retrasarla 12 horas para que siempre esté disponible cuando quiera escucharla, de manera que su zona horaria esté sincronizada con mi zona horaria.
Imagino esto como un script que se ejecuta en el host de mi servidor.
Un enfoque ingenuo sería simplemente asignar suficiente carnero en un ringbuffer para almacenar todo el retraso de 12 horas, y conectar la salida del streamripper. Pero la transmisión es un mp3 de 128 kbps, lo que significaría (128/8) * 60 * 60 = ~ 56 MB por hora, o 675 MB para todo el búfer de 12 horas, lo que no es realmente tan práctico. Además, podría tener que lidiar con el host de mi servidor simplemente matando el proceso después de un cierto tiempo de espera.
Entonces, ¿cuáles son algunas estrategias que podrían ser prácticas?
Solución
Un destripador de flujo sería la manera Fácil, y probablemente la manera Correcta, pero si quieres hacerlo de la manera Programadora ...
- La mayoría de las máquinas de desarrollo tienen bastante RAM. ¿Estás SEGURO de que no puedes ahorrar 675 MB?
- En lugar de almacenar el resultado en un búfer, ¿no puede almacenarlo en un archivo o archivos, por ejemplo, una hora a la vez? (esencialmente, estaría escribiendo su propio extractor de secuencias)
- Convierta la transmisión a una tasa de bits más baja, si puede tolerar la pérdida de calidad
Otros consejos
¿Por qué no lo descarga con un destripador de flujo como Ripshout o algo así?
para responder mi propia pregunta, aquí hay un script que se inicia como un trabajo cron cada 30 minutos. descarga el flujo entrante en fragmentos de 5 minutos (o establecido por FILE _ SECONDS) en un directorio particular. los bordes de bloque se sincronizan con el reloj, y no comienza a escribir hasta el fin del fragmento de tiempo actual, por lo que los cronjobs en ejecución pueden superponerse sin duplicar datos o dejar espacios vacíos. los archivos se denominan como (tiempo de época% número de segundos en 24 horas) .str.
todavía no he creado un reproductor, pero el plan era establecer el directorio de salida en un lugar accesible desde la web, y escribir un script que se ejecute localmente que use el mismo código de cálculo de marca de tiempo que aquí para acceder secuencialmente (marca de tiempo Hace 12 horas) .str, vuélvalos a juntar nuevamente y luego configúrelos como un servidor de difusión local. entonces podría apuntar mi reproductor de música a http: // localhost: port y obtenerlo.
edit: Nueva versión con tiempos de espera y mejor comprobación de condiciones de error, además de un buen archivo de registro. actualmente se ejecuta sin problemas en mi webhost compartido (barato), sin problemas.
#!/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()