Задержка потока Shoutcast на 12 часов (Linux / Bash) - PullRequest
1 голос
/ 03 декабря 2008

Я живу на другом конце света от моего дома (GMT + 1 сейчас, GMT + 13 дома), и я скучаю по своей старой наземной радиостанции. У него есть поток Shoutcast, и я хотел бы просто отложить его на 12 часов, чтобы он всегда был доступен, когда я хочу его прослушать, таким образом, чтобы его часовой пояс был синхронизирован с моим часовым поясом.

Я предполагаю, что это сценарий, выполняемый на моем хосте сервера.

Наивным подходом было бы просто выделить достаточное количество оперативной памяти в кольцевом буфере, чтобы сохранить всю 12-часовую задержку, и направить выходной поток из потокового преобразователя. Но поток - это mp3 со скоростью 128 кбит / с, что будет означать (128/8) * 60 * 60 = ~ 56 МБ в час или 675 МБ для всего 12-часового буфера, что на самом деле не так практично. Кроме того, мне, возможно, придется иметь дело с моим хостом сервера, просто убивающим процесс после определенного времени ожидания.

Итак, какие стратегии могут быть практичными?

Ответы [ 3 ]

1 голос
/ 04 декабря 2008

Потоковый риппер был бы простым способом, и, вероятно, правильным, но если вы хотите сделать это способом программиста ....

  • Большинство машин для разработки имеют довольно мало оперативной памяти. Вы уверены, что не можете сэкономить 675 МБ?
  • Вместо того, чтобы сохранять выходные данные в буфере, разве вы не можете сохранить их в файле или файлах, скажем, по часу? (по сути, вы бы написали свой собственный потоковый риппер)
  • Преобразование потока в более низкую скорость передачи битов, если вы можете допустить потерю качества
1 голос
/ 03 декабря 2008

Почему бы вам просто не скачать его с потоковым риппером, например Ripshout или чем-то еще?

0 голосов
/ 04 декабря 2008

чтобы ответить на мой собственный вопрос, вот скрипт, который запускается как задание cron каждые 30 минут. он сбрасывает входящий поток за 5 минут (или задается с помощью FILE _ SECONDS) в конкретный каталог. границы блоков синхронизируются с часами, и запись не начинается до тех пор, пока end текущего временного блока не будет, поэтому работающие cronjobs могут перекрываться, не удваивая данные и не оставляя пробелов. файлы называются (время эпохи% количество секунд в 24 часах) .str.

Я еще не сделал игрока, но планировалось установить выходной каталог где-нибудь доступным через Интернет, и написать скрипт для локального запуска, который использует тот же код для вычисления метки времени, что и здесь, для последовательного доступа (метка времени 12 часов назад) .str, соберите их снова вместе, а затем локально установите в качестве сервера shoutcast. тогда я мог бы просто указать свой музыкальный проигрыватель на http://localhost:port и получить его.

edit: Новая версия с тайм-аутами и улучшенной проверкой состояния ошибок, а также красивый файл журнала. в настоящее время он работает без проблем на моем (дешевом) общем веб-хосте, без проблем.

#!/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()
...