Shoutcastストリームを12時間遅らせる(Linux / bash)
-
19-08-2019 - |
質問
私は自宅から世界の反対側に住んでいます(現在GMT + 1、GMT + 13が自宅です)。古い地上のラジオ局が恋しいです。 Shoutcastストリームがあり、12時間遅延するだけで、タイムゾーンがタイムゾーンと同期するように、いつでも視聴できるようになります。
これをサーバーホストで実行されるスクリプトとして想定しています。
単純なアプローチは、12時間の遅延全体を保存するのに十分なRAMをリングバッファーに割り当て、streamripperからの出力をパイプすることです。しかし、ストリームは128kbps mp3であり、これは(128/8)* 60 * 60 = 1時間あたり約56MB、または12時間のバッファー全体で675MBを意味しますが、実際にはあまり実用的ではありません。さらに、特定のタイムアウト後にプロセスを強制終了するだけでサーバーホストを処理する必要がある場合があります。
では、実際に実用的な戦略は何ですか?
解決
ストリームリッパーは簡単な方法であり、おそらく正しい方法ですが、プログラマーの方法を使用する場合は...
- ほとんどの開発マシンにはかなりのRAMがあります。 675 MBを節約することはできませんか?
- 出力をバッファに保存するのではなく、1時間に1つまたは複数のファイルに出力を保存することはできませんか? (基本的に、独自のストリームリッパーを作成することになります)
- 品質の低下を許容できる場合は、ストリームをより低いビットレートに変換します
他のヒント
なぜRipshoutなどのストリームリッパーでダウンロードしないのですか?
自分の質問に答えるために、30分ごとにcronジョブとして起動するスクリプトを次に示します。着信ストリームを5分間のチャンク(またはFILE _ SECONDSで設定)で特定のディレクトリにダンプします。ブロック境界はクロックに同期され、現在のタイムチャンクの end まで書き込みを開始しないため、実行中のcronjobはデータを2倍にしたりギャップを残したりせずにオーバーラップできます。ファイルの名前は(エポック時間%24時間の秒数).strです。
iはまだプレーヤーを作成していませんが、出力ディレクトリをWebアクセス可能な場所に設定し、ここで同じタイムスタンプ計算コードを使用してローカルに実行するスクリプトを記述し、順次アクセスする(タイムスタンプ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()