Shell 将Shoutcast流延迟12小时(Linux/bash)
我住在世界的另一边,远离我的家(现在是GMT+1,GMT+13是家),我想念我的老地面电台。它有一个Shoutcast流,我只想把它延迟12个小时,这样当我想听它的时候,它总是可用的,这样它的时区就会与我的时区同步 我设想这是一个在我的服务器主机上运行的脚本 一个简单的方法是在一个ringbuffer中分配足够的ram来存储整个12小时的延迟,并通过管道输入streamripper的输出。但是这个流是128kbps的mp3,这意味着(128/8)*60*60=~56MB/小时,或者整个12小时的缓冲区是675MB,这实际上并不实用。另外,我可能不得不处理服务器主机在某个超时后停止进程的问题Shell 将Shoutcast流延迟12小时(Linux/bash),shell,audio-recording,shoutcast,Shell,Audio Recording,Shoutcast,我住在世界的另一边,远离我的家(现在是GMT+1,GMT+13是家),我想念我的老地面电台。它有一个Shoutcast流,我只想把它延迟12个小时,这样当我想听它的时候,它总是可用的,这样它的时区就会与我的时区同步 我设想这是一个在我的服务器主机上运行的脚本 一个简单的方法是在一个ringbuffer中分配足够的ram来存储整个12小时的延迟,并通过管道输入streamripper的输出。但是这个流是128kbps的mp3,这意味着(128/8)*60*60=~56MB/小时,或者整个12小时的
那么,哪些策略可能是实际可行的呢?为什么不使用Ripshout之类的流裂土器下载呢?流裂土器是一种简单的方法,可能是正确的方法,但是如果你想用程序员的方法来做
- 大多数开发机器都有相当多的RAM。你确定不能腾出675 MB吗
- 与其将输出存储在缓冲区中,不如将其存储在一个或多个文件中,比如一次存储一个小时?(基本上,您将编写自己的流裂土器)
- 如果可以容忍质量损失,请将流转换为较低的比特率
- 为了回答我自己的问题,这里有一个脚本,它每30分钟作为cron作业启动一次。它将传入流以5分钟的块(或按文件uu秒设置)转储到特定目录。块边框与时钟同步,直到当前时间块结束时才开始写入,因此运行的cronjobs可以重叠,而不会使数据加倍或留下间隙。文件名为(历元时间%24小时内的秒数).str
我还没有制作播放器,但计划是将输出目录设置为web可访问的位置,并编写一个本地运行的脚本,该脚本使用与此处相同的时间戳计算代码来顺序访问(12小时前的时间戳)。str,再次将它们固定在一起,然后在本地设置为shoutcast服务器。然后我就可以把我的音乐播放器指向它了
编辑:具有超时和更好的错误条件检查的新版本,加上漂亮的日志文件。这是目前在我的(便宜的)共享网络主机上运行无故障,没有任何问题
#/usr/bin/python
导入时间
导入URL库
导入日期时间
导入操作系统
导入套接字
#每个文件的秒数
文件\u秒=300
#跑30分钟
运行时间=60*30
#每个读取块的大小(字节)
#16384=1秒
块大小=16384
最大超时=10
#保存文件的位置
OUTPUT_DIRECTORY=“dir/”
#原始流的URL
URL=”http://url/path:port"
调试=真
日志=无
socket.setdefaulttimeout(10)
类DatestampedWriter:
#输出路径必须有尾随“/”
定义初始化(自身、输出路径、运行秒):
self.path=输出路径
self.file=None
#需要为-1以避免在0是真实时间戳时出现问题
self.curr\u timestamp=-1
self.running=False
#在当前时间段的_结束_之前不要启动
#因此,将初始时间戳计算为(现在+文件秒)
self.initial\u timestamp=self.CalcTimestamp(文件\u秒)
self.final_timestamp=self.CalcTimestamp(运行秒)
如果调试:
log=open(输出目录+“log”+str(self.initial\u时间戳)+“.txt”,“w”)
log.write(“初始时间戳”+str(self.initial\u timestamp)+”,final“+str(self.final\u timestamp)+”(diff“+str(self.final\u timestamp-self.initial\u timestamp)+”)\n)
self.log=log
def关闭(自):
如果self.file!=无:
self.file.close()文件
#写出buf
#当我们应该停止时返回True
def写入(自身,buf):
#检查是否打开了正确的文件
#获取时间戳
timestamp=self.CalcTimestamp()
如果不是自动运行:
#我们应该开始吗?
如果时间戳==self.initial\u时间戳:
如果调试:
self.log.write(“现在开始运行\n”)
self.log.flush()
self.running=True
#我们应该打开一个新文件吗?
如果自运行和时间戳!=self.curr\u时间戳:
如果调试:
self.log.write(“新时间戳”+str(时间戳)+“\n”)
self.log.flush()
#关闭旧文件
如果(self.file!=无):
self.file.close()文件
#是时候停下来了?
如果(self.curr\u timestamp==self.final\u timestamp):
如果调试:
self.log.write(“--停止时间\n”)
self.log.flush()
self.running=False
返回真值
#打开新文件
filename=self.path+str(时间戳)+“.str”
#如果不存在os.path.exists(文件名):
self.file=打开(文件名为“w”)
self.curr\u timestamp=int(时间戳)
#其他:
#哦
#如果调试:
#self.log.write(“尝试打开但失败,已存在\n”)
#self.running=False
#现在写入字节
如果自动运行:
#打印(“写入”+str(len(buf)))
self.file.write(buf)
返回错误
def CalcTimestamp(自身,秒\u偏移=0):
t=datetime.datetime.now()
秒=time.mktime(t.timetuple())+秒偏移量
#文件\u秒间隔,24小时天
时间戳=秒-(秒%FILE\u秒)
时间戳=时间戳%86400
返回int(时间戳)
writer=DatestampedWriter(输出目录,运行时间)
writer_finished=False
#在运行期间<(运行_
#!/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()