Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
7ba5399
added optional commandline options
primatic Jun 1, 2017
8987258
fix segmentation fault on raspberrypi
primatic Jun 2, 2017
1bc1c98
updated readme
primatic Jun 2, 2017
5820f1e
updated README.mkd
primatic Jun 2, 2017
501aad5
fixed pls parsing
primatic Sep 12, 2017
488cba8
update remote
primatic Mar 10, 2019
c5a1cc6
new file only when stream_title changes
smitdol Aug 16, 2020
68bc8d8
extra logging and no-metainfo handling
smitdol Aug 20, 2020
0c6d465
handling redirects and timestamping basefilenam
smitdol Aug 27, 2020
619de6e
HTTP 1.0
smitdol Aug 28, 2020
7a466e8
updated README.mkd
smitdol Aug 28, 2020
fc62057
retry if connection closed prematurely
smitdol Aug 30, 2020
750dc91
update README
smitdol Aug 30, 2020
ceaf2f5
added repeat functionality
smitdol Aug 30, 2020
bef2c29
update documentation
smitdol Aug 31, 2020
8e6b1b7
added taglib for id3tag
smitdol Sep 1, 2020
34a697f
split stream_title in title and artist and add id3tag
smitdol Sep 2, 2020
a5f2ed5
cleanup Makefile
smitdol Sep 2, 2020
89fbfeb
filename using dot separation instead of dash
smitdol Sep 14, 2020
4a257e0
newfilename function added, proglog removed
smitdol Sep 14, 2020
43c0007
fix playlist handling
smitdol Sep 14, 2020
ceea33c
handling special non-ascii cahracters
smitdol Oct 24, 2020
a032105
option to add file extension; fix SwapOfs when duration == 0
smitdol Oct 25, 2020
81f4cee
refactor load_stream, parameters to stream type
smitdol Oct 25, 2020
f904f0c
aac metadata
smitdol Nov 1, 2020
fc3a5d1
refactoring while looking for segfault, seems cookie related...?
smitdol Nov 8, 2020
6f2ae76
fix memleak
Nov 8, 2020
7825d32
option to specify log folder; handle extended ascii
smitdol Nov 12, 2020
309464e
ascii_table
smitdol Nov 12, 2020
b23d75d
create logfolder if not exist
smitdol Nov 13, 2020
bbc5899
work around curl_easy_getinfo TIMEOPT max_int limitation
smitdol Dec 2, 2020
291d03f
additional logging
smitdol Dec 5, 2020
58a06d5
record specific title
smitdol Dec 6, 2020
3875318
increment metadata_count only when creating new output file
smitdol Dec 13, 2020
cc47932
only log streamTitle changes
smitdol Dec 27, 2020
f501814
added timestamp to SwapOfs logline
smitdol Feb 8, 2021
bb4ce5d
added stationname option
smitdol Apr 18, 2021
2ee8983
support comma separated list of titles to record
smitdol Apr 24, 2021
c565114
preserve string for tokenizer
smitdol Apr 24, 2021
bdf099d
refactor into plog
smitdol May 1, 2021
6e1be2e
update README
smitdol May 1, 2021
5e0f271
markdown fixes
smitdol May 1, 2021
ca4e507
fix segmentation fault
smitdol Jun 6, 2021
1cb6ebf
handle timeinfo in newfilename and check for file existence
smitdol Jun 20, 2021
9cdee55
fix log date
smitdol Oct 17, 2021
3070d83
handle no title to match
smitdol Oct 17, 2021
4a3b96c
fix metaint not found result in disfunction
smitdol Dec 17, 2021
2790b33
use strstr iso strtok and add album tag
smitdol Apr 18, 2022
04d2fab
spaces to tabs
smitdol Apr 18, 2022
aa6f38b
metadata update with newfilename
smitdol May 1, 2022
e4e7e30
change default artist - title
smitdol May 8, 2022
a0f3d58
update readme
smitdol May 8, 2022
af33895
prevent duplicate prefixes
smitdol Jul 21, 2022
315a38e
convert streamtitle to pascal casing
smitdol Jan 29, 2023
1ec9ede
also log to stdout and 2 iso 3 digits for filename sequence number
smitdol Mar 1, 2023
f59b780
added station to stream
smitdol Mar 29, 2023
0776e7f
TITLE_SIZE
Mar 29, 2023
e6102eb
clean buffers
smitdol Mar 29, 2023
3d141c5
fix empty title (not) becoming station name
smitdol Apr 2, 2023
dec33c9
fix CamelCase
smitdol Apr 19, 2023
ed4ee5c
use strftime for log
smitdol Apr 21, 2023
0b80f0e
fix mising space after timestamp and refactor log
smitdol Apr 26, 2023
f9e95e1
another camel-case
smitdol May 12, 2023
03a4f33
added option to ignore a stream_title change
smitdol Oct 31, 2023
3f89752
fix to_ignore + add more logging
smitdol Nov 5, 2023
5d92aee
add ignore option for changes in title
smitdol May 25, 2025
676ca30
adding -z option
smitdol Sep 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ OBJ = $(SRC:src/%.c=$(BUILD_PATH)/%.o)
INCLUDES = -Iinclude \
-I/usr/include

CFLAGS = -W -Wall -g
LDFLAGS = -L/usr/lib -lcurl
CFLAGS = -W -Wall -g -D_GNU_SOURCE
LDFLAGS = -L/usr/lib -lcurl -ltag_c

all: prepare $(EXEC)

Expand Down
127 changes: 109 additions & 18 deletions README.mkd
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
SHOUTcast Recorder
==================

SHOUTcast Recorder is able to record and store SHOUTcast stream automatically
into separate mp3 files. This software was initially conceived to understand
the SHOUTcast protocol, not really to record web radios ;) The fun part is to
play with the protocol !
SHOUTcast Recorder is able to record and store SHOUTcast stream automatically into separate mp3 files. This software was initially conceived to understand the SHOUTcast protocol, not really to record web radios ;) The fun part is to play with the protocol !

Dependencies
------------

- libcurl
- sudo apt-get install libcurl-dev
- sudo apt-get install libcurl4-openssl-dev
- taglib
- sudo apt-get install libtag1-dev
- sudo apt-get install libtagc0-dev


How does it work ?
------------------
Expand All @@ -23,27 +26,113 @@ How does it work ?
- At the end of each block, we write gathered MP3 data into an external file
- Now we can enjoy downloaded music :)

How to run it
-------------

Here are two examples :
How to build it
---------------

./shoutr -u http://88.190.24.47:80
- use make to build
- artefact is in build folder
- see Makefile

or

./shoutr -p frequence3.pls
How to run it
-------------

Usage:

./shoutr [-p <playlist>|-u <stream_url>] [OPTIONS]

Examples :

./shoutr -u http://88.190.24.47:80 -f ./%w-%H -d 3580

./shoutr -p frequence3.pls -l ./log

OPTIONS:

-d duration defines the recording length in seconds (see CURLOPT_TIMEOUT)
-e extension fileextension (default: mp3)
-f basefilename defines the base name of the mp3 file(s) created (default: radio)
-i ignore stream_title (change) to ignore (do not create a new file)
-l logfolder (rel/abs) path to folder for log (default: current folder)
-n name name of station (default radio)
-r repeat repeat the recording specified number of times
-s id3tags split stream_title (on ' - ') into artist - title (0) or title - artist (1, default)
-t title (part of) title to record (default any) (tip: use '-' to record only music)
-x proxy defines proxy to use (see CURLOPT_PROXY)

without these additional commandline options specified

no proxy is used
libcurl respects the proxy environment variables named http_proxy, ftp_proxy, sftp_proxy etc., see CURLOPT_PROXY
basename will be radio###.mp3
### is incrementing number
incrementing number can be used for sorting.
as metadata is never changing exacty on the moment the song changes,
a fluent playback is only possible in recording order (hence incrementing number)
incrementing number will increment when
metadata streamtitle changes
repeat > 0 and duration passes
basename can handle timestamp directives (see strftime)
basename will be appended with name of station (if provided with -n) and downloaded music using metadata (if available)
duration will be infinite (see CURLOPT_TIMEOUT)
repeat will be 0 (no repeat)
repeat will
reuse/continue the recording using the existing connection
avoid establishing new connection (preventing new connection pub)
change incrementing number
create a separate recording file for each repeat
not change basename (weekday nor hour nor any supplied timestamp directive)
N.B. a repeat set to 2 will produce 3 recordings (0, 1=1st repeat, 2=2nd repeat)
(of the same stream and of the same duration)
non-empty stream_title will be used for id3tag assuming artist - title


cron example :

SHELL=/bin/bash
log=/HDD/log/crontab.
dt=date +%Y%m%d

stream=http://88.190.24.47:80
0 10 * * * cd /HDD; ./shoutr -u $stream -f ./\%u-10 -d 3580 -r 1 >> $log$($dt).log 2>&1


copy recordings in recording order :


#!/bin/bash
SECONDS=0
TEST=
IFS=$'\n'
DRIVE="$(dirname "$0")"
DST="${DRIVE}/.."
LOG="${DRIVE}/copyit.LOG"
echo "" > $LOG

function cpit {
$TEST mkdir -pv "${DST}/${CWD}" | tee -a $LOG
for i in $( find "${SRC}${CWD}" -type f -mtime -6 | sort )
do
$TEST rsync --min-size=1mb -avh --no-p --no-g --chmod=ugo=rwX --modify-window=2 "$i" "${DST}/${CWD}" | tee -a $LOG
done
}

SRC=/mnt/nfs/HDD/
for G in "CD01" "CD02" "CD04"
do
CWD=$G
echo $CWD $DST
cpit
done
echo "Script finished in $SECONDS seconds=> $(($SECONDS/60)) min $(($SECONDS%60)) sec" | tee -a $LOG

TODO
----

Some interesting features to add :

- add option --quiet
- add option --ncurses
- add a big shoucast radio list with lots of radios
- add shoutr_start and shoutr_stop functions.
- add id3tags
- more id3tags (taglib) (artist and title depends on station)
- see www.radio-browser.info for a big shoucast radio list


LICENSE
-------
Expand All @@ -53,5 +142,7 @@ See the file COPYING for details.

Contact
-------

Yoann Sculo - <yoann.sculo@gmail.com>

------
markdown by http://remarkableapp.github.io/
8 changes: 8 additions & 0 deletions include/curl.h
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
#ifndef __CURL_H_
#define __CURL_H_

void SwapOfs(void *p);
int read_stream(Stream *stream);

#if LIBCURL_VERSION_NUM >= 0x072000
int xferinfo(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
#else
int older_progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow);
#endif


#endif // __CURL_H_
3 changes: 2 additions & 1 deletion include/log.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#ifndef __LOG_H_
#define __LOG_H_

int log_open_files(void);
int log_open_files(char* folder);
int log_close_files(void);
void slog(char *line);
void slog_prog(char *line);
void plog(char *fmt, ...);

#endif
1 change: 1 addition & 0 deletions include/parsing.h
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@

size_t parse_data(void *ptr, size_t size, size_t nmemb, void *userdata);
size_t parse_header(void *ptr, size_t size, size_t nmemb, void *userdata);
4 changes: 2 additions & 2 deletions include/pls.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
#define MAX_LINE_LENGTH 500

typedef struct {
char file[500];
char title[500];
char file[MAX_LINE_LENGTH];
char title[MAX_LINE_LENGTH];
int length;
} PlsEntry;

Expand Down
3 changes: 2 additions & 1 deletion include/shoutcast.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

#include "types.h"

int free_stream(Stream *stream);
int load_stream(Stream *stream, const char *url);
void global_listener(Stream *stream, char *buffer);
int write_data(Stream *stream, size_t *size);
int write_data(Stream *stream);

#endif // __SHOUTCAST_H_
27 changes: 20 additions & 7 deletions include/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#define TRUE 1
#define FALSE 0
#define TITLE_SIZE 160

typedef enum {
E_STATUS_NONE,
Expand All @@ -18,7 +19,7 @@ typedef struct
char icy_name[500];
char icy_notice1[500];
char icy_notice2[500];
char icy_genre[255];
char icy_genre[TITLE_SIZE];
char icy_pub[10];
char icy_br[10]; // bitrate

Expand Down Expand Up @@ -47,20 +48,32 @@ typedef struct

typedef struct
{
char url[255]; // Stream url
char url[TITLE_SIZE]; // Stream url
char proxy[TITLE_SIZE]; // Network proxy
char basefilename[TITLE_SIZE]; // basefilename of output file
char filename[TITLE_SIZE]; // current filename of output file
char ext[TITLE_SIZE]; // current extension of output file
char onlytitle[TITLE_SIZE]; // record only if title contains onlytitle
char to_ignore[TITLE_SIZE]; // ignore stream_title changes when including this string
unsigned int duration; // max recording duration
unsigned int repeat; // max recording repeats
FILE *output_stream; // Output MP3 file
char stream_title[500]; // Current title

char stream_title[TITLE_SIZE]; // Current title
char station[TITLE_SIZE]; // station
int TA; // title = <Title> - <Artist> else <Artist> - <Title>
parsing_status status;

ICYHeader header; // Stream header (won't change after being set)
MetaData metadata; // Stream metadata (will change during session)
Mp3Data mp3data; // MP3 raw data
ICYHeader header; // Stream header (won't change after being set)
MetaData metadata; // Stream metadata (will change during session)
Mp3Data mp3data; // MP3 raw data

unsigned int bytes_count; // Number of bytes received since last metadata block
unsigned int bytes_count_total; // Number of bytes received since beginning
unsigned int blocks_count; // Number of HTTP blocks received
unsigned int metadata_count; // Number of metadata blocks received since beginning
int ignoring; // ignoring title change
} Stream;

void newfilename(Stream* stream,const char* title);

#endif // __TYPES_H_
8 changes: 4 additions & 4 deletions radios/frequence3.pls
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[playlist]
NumberOfEntries=2
File1=http://stream-hautdebit.frequence3.net:8000/
Title1=[Serveur Generique] - Frequence3 - Une Rafale de Tubes ! [HautDebit]
File1=http://ice.stream.frequence3.net/frequence3-128.mp3
Title1=[Serveur Generique] - Frequence3 - Une Rafale de Tubes !
Length1=-1
File2=http://stream-hautdebit.frequence3.net:8000/
Title2=[Serveur Generique] - Frequence3 - Une Rafale de Tubes ! [HautDebit]
File2=http://ice.stream.frequence3.net/frequence3-32.mp3
Title2=[Serveur Generique] - Frequence3 - Une Rafale de Tubes !
Length2=-1
Version=2
20 changes: 20 additions & 0 deletions shoutcastRecorder/shoutcastRecorder.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "shoutcastRecorder", "shoutcastRecorder\shoutcastRecorder.vcxproj", "{7EFF25AC-DF8A-44AF-83EE-B20CB7056DB7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Release|Win32 = Release|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7EFF25AC-DF8A-44AF-83EE-B20CB7056DB7}.Debug|Win32.ActiveCfg = Debug|Win32
{7EFF25AC-DF8A-44AF-83EE-B20CB7056DB7}.Debug|Win32.Build.0 = Debug|Win32
{7EFF25AC-DF8A-44AF-83EE-B20CB7056DB7}.Release|Win32.ActiveCfg = Release|Win32
{7EFF25AC-DF8A-44AF-83EE-B20CB7056DB7}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
40 changes: 40 additions & 0 deletions shoutcastRecorder/shoutcastRecorder/ReadMe.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
========================================================================
CONSOLE APPLICATION : shoutcastRecorder Project Overview
========================================================================

AppWizard has created this shoutcastRecorder application for you.

This file contains a summary of what you will find in each of the files that
make up your shoutcastRecorder application.


shoutcastRecorder.vcxproj
This is the main project file for VC++ projects generated using an Application Wizard.
It contains information about the version of Visual C++ that generated the file, and
information about the platforms, configurations, and project features selected with the
Application Wizard.

shoutcastRecorder.vcxproj.filters
This is the filters file for VC++ projects generated using an Application Wizard.
It contains information about the association between the files in your project
and the filters. This association is used in the IDE to show grouping of files with
similar extensions under a specific node (for e.g. ".cpp" files are associated with the
"Source Files" filter).

shoutcastRecorder.cpp
This is the main application source file.

/////////////////////////////////////////////////////////////////////////////
Other standard files:

StdAfx.h, StdAfx.cpp
These files are used to build a precompiled header (PCH) file
named shoutcastRecorder.pch and a precompiled types file named StdAfx.obj.

/////////////////////////////////////////////////////////////////////////////
Other notes:

AppWizard uses "TODO:" comments to indicate parts of the source code you
should add to or customize.

/////////////////////////////////////////////////////////////////////////////
Loading