Compare commits

..

No commits in common. "8182cf2e07799d462dd5584dfa49c145cf160fbc" and "c65e34b283545df606c730ad59d91dc02ee46905" have entirely different histories.

3 changed files with 38 additions and 106 deletions

6
README
View file

@ -8,8 +8,6 @@ Installation
depending on which platform you are using this program, e.g.:
* Linux: $HOME/.config/radiorec/settings.ini or
* Windows: %LOCALAPPDATA%/radiorec/settings.ini
or use the commandline option '-s' to specify the location of the
settings.ini file.
- Adjust the settings to your needs. You can happily add more radio stations
to the STATIONS section.
!!! Check at least the the target_dir !!!
@ -56,8 +54,8 @@ how to schedule your recording tasks.
Known problems
==============
The Windows command line (cmd and powershell) still has problems with UTF-8.
Using the --verbose option might cause the script to crash with an
UnicodeEncodeError. If you want to avoid the crash, you have to do both,
Using the --verbose option might cause the script to crash with an
UnicodeEncodeError. If you want to avoid the crash, you have to do both,
change the command line codepage and the font. For example, doing a
"chcp 65001" and changing the font to "Lucidia Console" should help.

View file

@ -26,15 +26,7 @@ import os
import stat
import sys
import threading
import urllib3
import logging
import time
from urllib3.exceptions import MaxRetryError
logging.basicConfig(level=logging.DEBUG)
def print_time():
return time.strftime("%Y-%m-%d %H:%M:%S")
import urllib.request
def check_duration(value):
@ -42,57 +34,45 @@ def check_duration(value):
value = int(value)
except ValueError:
raise argparse.ArgumentTypeError(
'Duration in minutes must be a positive integer.')
'Duration must be a positive integer.')
if value < 1:
raise argparse.ArgumentTypeError(
'Duration in minutes must be a positive integer.')
'Duration must be a positive integer.')
else:
return value
def read_settings(args):
def read_settings():
settings_base_dir = ''
if args.settings:
settings_base_dir = args.settings
elif sys.platform.startswith('linux'):
if sys.platform.startswith('linux'):
settings_base_dir = os.getenv(
'HOME') + os.sep + '.config' + os.sep + 'radiorec'
elif sys.platform == 'win32':
settings_base_dir = os.getenv('LOCALAPPDATA') + os.sep + 'radiorec'
elif sys.platform == 'darwin':
settings_base_dir = os.getenv('HOME') + os.sep + 'Library' + os.sep + \
'Application Support' + os.sep + 'radiorec'
settings_base_dir = os.getenv('HOME') + os.sep + 'Library' + os.sep + 'Application Support' + os.sep + 'radiorec'
settings_base_dir += os.sep
config = configparser.ConfigParser()
try:
config.read_file(open(settings_base_dir + 'settings.ini'))
except FileNotFoundError as err:
print(str(err))
print('Please copy/create the settings file to/in the appropriate location.')
print('Please copy/create the settings file to/in the appropriate '
'location.')
sys.exit()
return dict(config.items())
def record_worker(stoprec, streamurl, target_dir, args):
pool = urllib3.PoolManager()
conn = pool.request('GET', streamurl, preload_content=False)
conn.auto_close = False
if conn.status != 200:
conn.release_conn()
time.sleep(10)
verboseprint(print_time() + " ... Waited to return for retry bcof status " + str(conn.status))
return
conn = urllib.request.urlopen(streamurl)
cur_dt_string = datetime.datetime.now().strftime('%Y-%m-%dT%H_%M_%S')
filename = target_dir + os.sep + cur_dt_string + "_" + args.station
if args.name:
filename += '_' + args.name
content_type = conn.getheader('Content-Type')
if (content_type == 'audio/mpeg'):
if(content_type == 'audio/mpeg'):
filename += '.mp3'
elif(content_type == 'application/aacp' or content_type == 'audio/aacp'):
filename += '.aac'
elif(content_type == 'application/ogg' or content_type == 'audio/ogg'):
filename += '.ogg'
elif(content_type == 'audio/x-mpegurl'):
@ -102,22 +82,19 @@ def record_worker(stoprec, streamurl, target_dir, args):
print('Unknown content type "' + content_type + '". Assuming mp3.')
filename += '.mp3'
verboseprint(print_time() + " ... Writing to: " + filename + ", Content-Type: " + conn.getheader('Content-Type'))
with open(filename, 'wb') as target:
with open(filename, "wb") as target:
if args.public:
verboseprint('Apply public write permissions (Linux only)')
os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH)
os.chmod(filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP |
stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH)
verboseprint('Recording ' + args.station + '...')
while(not stoprec.is_set() and not conn.closed):
target.write(conn.read(1024))
verboseprint(print_time() + " ... Connection closed = " + str(conn.closed))
conn.release_conn()
def record(args):
settings = read_settings(args)
settings = read_settings()
streamurl = ''
tmpstr = ''
global verboseprint
verboseprint = print if args.verbose else lambda *a, **k: None
@ -128,72 +105,37 @@ def record(args):
sys.exit()
if streamurl.endswith('.m3u'):
verboseprint('Seems to be an M3U playlist. Trying to parse...')
pool = urllib3.PoolManager()
try:
remotefile = pool.request('GET', streamurl)
except MaxRetryError:
logging.getLogger(__name__).error('The URL of the station is somehow faulty! Check' +
args.station + ' in the Settings!')
sys.exit()
if remotefile.status != 200:
logging.getLogger(__name__).error(
'The URL of the station is somehow faulty! Check' + args.station + ' in the Settings!')
sys.exit(1)
else:
for line in remotefile.data.decode().split():
if not line.startswith('#') and len(line) > 1 and line.endswith('mp3'):
tmpstr = line
with urllib.request.urlopen(streamurl) as remotefile:
for line in remotefile:
if not line.decode('utf-8').startswith('#') and len(line) > 1:
tmpstr = line.decode('utf-8')
break
if not tmpstr:
logging.getLogger(__name__).error('Could not find a mp3 stream')
sys.exit(1)
else:
streamurl = tmpstr
verboseprint(print_time() + " ... Stream URL: " + streamurl)
streamurl = tmpstr
verboseprint('stream url: ' + streamurl)
target_dir = os.path.expandvars(settings['GLOBAL']['target_dir'])
if not os.path.isdir(target_dir):
try:
os.mkdir(target_dir)
except FileNotFoundError:
logging.getLogger(__name__).error('Target directory not found! Check that ' +
target_dir + ' is a valid folder!')
sys.exit(1)
started_at = time.time()
should_end_at = started_at + (args.duration * 60)
remaining = (args.duration * 60)
stoprec = threading.Event()
# as long as recording is supposed to run
while time.time() < should_end_at:
stoprec = threading.Event()
recthread = threading.Thread(target=record_worker, args=(stoprec, streamurl, target_dir, args))
recthread.setDaemon(True)
recthread.start()
verboseprint(print_time() + " ... Started thread " + str(recthread) + " timeout: " +
str(remaining / 60) + " min")
recthread.join(remaining)
verboseprint(print_time() + " ... Came out of rec thread again")
recthread = threading.Thread(target=record_worker,
args=(stoprec, streamurl, target_dir, args))
recthread.setDaemon(True)
recthread.start()
recthread.join(args.duration * 60)
if recthread.is_alive:
stoprec.set()
verboseprint(print_time() + " ... Called stoprec.set()")
else:
verboseprint(print_time() + " ... recthread.is_alive = False")
remaining = should_end_at - time.time()
verboseprint(print_time() + " ... Remaining: " + str(remaining / 60) +
", Threads: " + str(threading.activeCount()))
if(recthread.is_alive):
stoprec.set()
def list(args):
settings = read_settings(args)
settings = read_settings()
for key in sorted(settings['STATIONS']):
print(key)
def main():
parser = argparse.ArgumentParser(description='This program records internet radio streams. '
'It is free software and comes with ABSOLUTELY NO WARRANTY.')
parser = argparse.ArgumentParser(description='This program records '
'internet radio streams. It is free '
'software and comes with ABSOLUTELY NO '
'WARRANTY.')
subparsers = parser.add_subparsers(help='sub-command help')
parser_record = subparsers.add_parser('record', help='Record a station')
parser_record.add_argument('station', type=str,
@ -208,14 +150,9 @@ def main():
help="Public write permissions (Linux only)")
parser_record.add_argument(
'-v', '--verbose', action='store_true', help="Verbose output")
parser_record.add_argument(
'-s', '--settings', nargs='?', type=str,
help="specify alternative location for settings.ini")
parser_record.set_defaults(func=record)
parser_list = subparsers.add_parser('list', help='List all known stations')
parser_list.set_defaults(func=list)
parser_list.add_argument('-s', '--settings', nargs='?', type=str,
help="specify alternative location for settings.ini")
if not len(sys.argv) > 1:
print('Error: No argument specified.\n')
@ -224,6 +161,5 @@ def main():
args = parser.parse_args()
args.func(args)
if __name__ == '__main__':
main()

View file

@ -1,15 +1,13 @@
[GLOBAL]
target_dir = $HOME/Arbeitsfläche
[STATIONS]
brklassik = http://streams.br-online.de/br-klassik_2.m3u
dkultur = http://www.deutschlandradio.de/streaming/dkultur.m3u
dlf = http://www.deutschlandradio.de/streaming/dlf.m3u
dwissen = http://dradio_mp3_dwissen_m.akacast.akamaistream.net/7/728/142684/v1/gnl.akacast.akamaistream.net/dradio_mp3_dwissen_m
erfplus = http://c14000-l.i.core.cdn.streamfarm.net/14000cina/live/3212erf_96/live_de_96.mp3
mdrklassik = http://avw.mdr.de/streams/284350-0_mp3_high.m3u
mdrklassik = http://avw.mdr.de/livestreams/mdr_klassik_live_128.m3u
radioeins = http://www.radioeins.de/live.m3u
swr2 = http://mp3-live.swr.de/swr2_m.m3u
wdr3 = http://wdr-wdr3-live.icecast.wdr.de/wdr/wdr3/live/mp3/256/stream.mp3
ndrkultur = http://www.ndr.de/resources/metadaten/audio/m3u/ndrkultur.m3u
wdr3 = http://www.wdr.de/wdrlive/media/mp3/wdr3_hq.m3u