Compare commits

...

19 commits

Author SHA1 Message Date
Martin Brodbeck 8182cf2e07
Merge pull request #16 from gjelsas/master
Trying to fix the .m3u decoding problem
2021-02-14 13:18:40 +01:00
georg aba237db2e Changed the sys.exit, if not tmpstr, the errorhandling, the creation of the Target Directory, and some PEP8 Styling things too.. 2020-12-25 18:27:36 +01:00
georg 5ba2de2f40 reverted temporary Settings... 2020-12-22 16:31:35 +01:00
georg 9793b69715 Added some checks for missing Targetfolder too many http requests and wrong urls.
Also changed the url of wdr3.
2020-12-22 16:28:18 +01:00
georg 3498a8d00b Settings revert to Arbeitsfläche 2020-12-21 23:05:50 +01:00
georg b4988e7299 No mor open with, plain fileobject 2020-12-21 22:34:20 +01:00
georg a8ad2c03e7 NDR in settings 2020-12-21 18:33:49 +01:00
georg ce9f83dc96 fixing m3u Problem 2020-11-06 20:23:08 +01:00
gjelsas a6b3de0c92
Merge pull request #1 from beedaddy/master
test
2020-11-06 20:18:30 +01:00
Martin Brodbeck b0f0ee3135
Merge pull request #14 from lastsamurai26/lastsamurai26-patch-1
Lastsamurai26 patch 1
2020-08-31 13:04:27 +02:00
Frank 4468ab74a3
update
update
2020-08-30 22:10:38 +02:00
Frank b723a4bc42
Update radiorec.py
changed __ to T 
Changed - to _
2020-08-29 23:17:01 +02:00
Frank e4a9406cc6
Update radiorec.py 2020-08-24 20:06:05 +02:00
Martin Brodbeck e3fa69e0dd
Merge pull request #12 from lastsamurai26/patch-2
Update radiorec.py
2019-11-07 12:26:47 +01:00
Frank c489d19299
Update radiorec.py
Add  content Type for aac
2019-11-01 23:00:28 +01:00
Martin Brodbeck 1453a7efbc
Merge pull request #11 from gjelsas/master
Adding Commandline option to set the location of the settings.ini file
2019-03-19 07:19:13 +01:00
gJsas 302f59e204 Merge branch 'master' of https://github.com/gjelsas/radiorec 2019-03-18 21:50:42 +01:00
gjelsas 8a70c601eb Add files via upload 2019-03-18 21:48:45 +01:00
gjelsas b9635e15eb
Add files via upload 2019-03-18 21:34:29 +01:00
3 changed files with 106 additions and 38 deletions

6
README
View file

@ -8,6 +8,8 @@ Installation
depending on which platform you are using this program, e.g.: depending on which platform you are using this program, e.g.:
* Linux: $HOME/.config/radiorec/settings.ini or * Linux: $HOME/.config/radiorec/settings.ini or
* Windows: %LOCALAPPDATA%/radiorec/settings.ini * 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 - Adjust the settings to your needs. You can happily add more radio stations
to the STATIONS section. to the STATIONS section.
!!! Check at least the the target_dir !!! !!! Check at least the the target_dir !!!
@ -54,8 +56,8 @@ how to schedule your recording tasks.
Known problems Known problems
============== ==============
The Windows command line (cmd and powershell) still has problems with UTF-8. 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 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, 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 change the command line codepage and the font. For example, doing a
"chcp 65001" and changing the font to "Lucidia Console" should help. "chcp 65001" and changing the font to "Lucidia Console" should help.

View file

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

View file

@ -1,13 +1,15 @@
[GLOBAL] [GLOBAL]
target_dir = $HOME/Arbeitsfläche target_dir = $HOME/Arbeitsfläche
[STATIONS] [STATIONS]
brklassik = http://streams.br-online.de/br-klassik_2.m3u brklassik = http://streams.br-online.de/br-klassik_2.m3u
dkultur = http://www.deutschlandradio.de/streaming/dkultur.m3u dkultur = http://www.deutschlandradio.de/streaming/dkultur.m3u
dlf = http://www.deutschlandradio.de/streaming/dlf.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 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 erfplus = http://c14000-l.i.core.cdn.streamfarm.net/14000cina/live/3212erf_96/live_de_96.mp3
mdrklassik = http://avw.mdr.de/livestreams/mdr_klassik_live_128.m3u mdrklassik = http://avw.mdr.de/streams/284350-0_mp3_high.m3u
radioeins = http://www.radioeins.de/live.m3u radioeins = http://www.radioeins.de/live.m3u
swr2 = http://mp3-live.swr.de/swr2_m.m3u swr2 = http://mp3-live.swr.de/swr2_m.m3u
wdr3 = http://www.wdr.de/wdrlive/media/mp3/wdr3_hq.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