diff --git a/Makefile b/Makefile index 8f65604..2894362 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/README.mkd b/README.mkd index 40d4bba..1e47069 100644 --- a/README.mkd +++ b/README.mkd @@ -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 ? ------------------ @@ -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 |-u ] [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 ------- @@ -53,5 +142,7 @@ See the file COPYING for details. Contact ------- - Yoann Sculo - + +------ +markdown by http://remarkableapp.github.io/ diff --git a/include/curl.h b/include/curl.h old mode 100644 new mode 100755 index 7f56f36..a1dd6a5 --- a/include/curl.h +++ b/include/curl.h @@ -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_ diff --git a/include/log.h b/include/log.h index 507bc44..8660a89 100644 --- a/include/log.h +++ b/include/log.h @@ -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 diff --git a/include/parsing.h b/include/parsing.h index 850ce72..f9cb979 100644 --- a/include/parsing.h +++ b/include/parsing.h @@ -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); \ No newline at end of file diff --git a/include/pls.h b/include/pls.h index d84d600..0e44621 100644 --- a/include/pls.h +++ b/include/pls.h @@ -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; diff --git a/include/shoutcast.h b/include/shoutcast.h index 48cbbff..21f9a97 100644 --- a/include/shoutcast.h +++ b/include/shoutcast.h @@ -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_ diff --git a/include/types.h b/include/types.h index 80d27fa..eba0bb0 100644 --- a/include/types.h +++ b/include/types.h @@ -3,6 +3,7 @@ #define TRUE 1 #define FALSE 0 +#define TITLE_SIZE 160 typedef enum { E_STATUS_NONE, @@ -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 @@ -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 = - <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_ diff --git a/radios/frequence3.pls b/radios/frequence3.pls index e551663..1036dc5 100644 --- a/radios/frequence3.pls +++ b/radios/frequence3.pls @@ -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 diff --git a/shoutcastRecorder/shoutcastRecorder.sln b/shoutcastRecorder/shoutcastRecorder.sln new file mode 100755 index 0000000..050de9a --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder.sln @@ -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 diff --git a/shoutcastRecorder/shoutcastRecorder/ReadMe.txt b/shoutcastRecorder/shoutcastRecorder/ReadMe.txt new file mode 100755 index 0000000..3935621 --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/ReadMe.txt @@ -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. + +///////////////////////////////////////////////////////////////////////////// diff --git a/shoutcastRecorder/shoutcastRecorder/getopt.c b/shoutcastRecorder/shoutcastRecorder/getopt.c new file mode 100755 index 0000000..1ea6e95 --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/getopt.c @@ -0,0 +1,117 @@ +// from https://gist.github.com/superwills/5815344 + +// Put this in a separate .h file (called "getopt.h"). +// The prototype for the header file is: +/* +#ifndef GETOPT_H +#define GETOPT_H +int getopt(int nargc, char * const nargv[], const char *ostr) ; +#endif +*/ + +#include "getopt.h" // make sure you construct the header file as dictated above + +/* +* Copyright (c) 1987, 1993, 1994 +* The Regents of the University of California. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. All advertising materials mentioning features or use of this software +* must display the following acknowledgement: +* This product includes software developed by the University of +* California, Berkeley and its contributors. +* 4. Neither the name of the University nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +*/ + +#include <string.h> +#include <stdio.h> + +int opterr = 1, /* if error message should be printed */ + optind = 1, /* index into parent argv vector */ + optopt, /* character checked for validity */ + optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int getopt(int nargc, char * const nargv[], const char *ostr) +{ + static char *place = EMSG; /* option letter processing */ + const char *oli; /* option letter list index */ + + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc || *(place = nargv[optind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-') { /* found "--" */ + ++optind; + place = EMSG; + return (-1); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (optopt == (int)'-') + return (-1); + if (!*place) + ++optind; + if (opterr && *ostr != ':') + (void)printf("illegal option -- %c\n", optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) + ++optind; + } + else { /* need an argument */ + if (*place) /* no white space */ + optarg = place; + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (opterr) + (void)printf("option requires an argument -- %c\n", optopt); + return (BADCH); + } + else /* white space */ + optarg = nargv[optind]; + place = EMSG; + ++optind; + } + return (optopt); /* dump back option letter */ +} diff --git a/shoutcastRecorder/shoutcastRecorder/getopt.h b/shoutcastRecorder/shoutcastRecorder/getopt.h new file mode 100755 index 0000000..8924258 --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/getopt.h @@ -0,0 +1,7 @@ +#ifndef GETOPT_H +#define GETOPT_H + +int getopt(int nargc, char * const nargv[], const char *ostr) ; + +extern char *optarg; /* argument associated with option */ +#endif diff --git a/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.cpp b/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.cpp new file mode 100755 index 0000000..7f028de --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.cpp @@ -0,0 +1,7 @@ +// shoutcastRecorder.cpp : Defines the entry point for the console application. +// + +#include "stdafx.h" + + + diff --git a/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.vcxproj b/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.vcxproj new file mode 100755 index 0000000..454797a --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.vcxproj @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{7EFF25AC-DF8A-44AF-83EE-B20CB7056DB7}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>shoutcastRecorder</RootNamespace> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <IncludePath>..\..\..\curl-master\include\curl;$(IncludePath)</IncludePath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <IncludePath>..\..\..\curl-master\include\curl;$(IncludePath)</IncludePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <PreprocessorDefinitions>WIN32;CURL_STATICLIB;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\include;..\..\..\curl-master\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalLibraryDirectories>..\..\..\curl-master\build\Win32\VC10\LIB Debug - DLL Windows SSPI</AdditionalLibraryDirectories> + <AdditionalDependencies>libcurld.lib;Ws2_32.lib;Wldap32.lib;crypt32.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <PrecompiledHeader> + </PrecompiledHeader> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;CURL_STATICLIB;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>..\..\include;..\..\..\curl-master\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <AdditionalLibraryDirectories>..\..\..\curl-master\build\Win32\VC10\LIB Debug - DLL Windows SSPI</AdditionalLibraryDirectories> + <AdditionalDependencies>libcurld.lib;Ws2_32.lib;Wldap32.lib;crypt32.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <None Include="ReadMe.txt" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\include\curl.h" /> + <ClInclude Include="..\..\include\files.h" /> + <ClInclude Include="..\..\include\header.h" /> + <ClInclude Include="..\..\include\icy-string.h" /> + <ClInclude Include="..\..\include\log.h" /> + <ClInclude Include="..\..\include\metadata.h" /> + <ClInclude Include="..\..\include\mp3data.h" /> + <ClInclude Include="..\..\include\parsing.h" /> + <ClInclude Include="..\..\include\pls.h" /> + <ClInclude Include="..\..\include\shoutcast.h" /> + <ClInclude Include="..\..\include\stream.h" /> + <ClInclude Include="..\..\include\types.h" /> + <ClInclude Include="getopt.h" /> + <ClInclude Include="stdafx.h" /> + <ClInclude Include="targetver.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="..\..\src\curl.c" /> + <ClCompile Include="..\..\src\files.c" /> + <ClCompile Include="..\..\src\header.c" /> + <ClCompile Include="..\..\src\icy-string.c" /> + <ClCompile Include="..\..\src\log.c" /> + <ClCompile Include="..\..\src\main.c" /> + <ClCompile Include="..\..\src\metadata.c" /> + <ClCompile Include="..\..\src\mp3data.c" /> + <ClCompile Include="..\..\src\pls.c" /> + <ClCompile Include="..\..\src\shoutcast.c" /> + <ClCompile Include="..\..\src\stream.c" /> + <ClCompile Include="getopt.c" /> + <ClCompile Include="shoutcastRecorder.cpp" /> + <ClCompile Include="stdafx.cpp" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.vcxproj.filters b/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.vcxproj.filters new file mode 100755 index 0000000..0b94490 --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/shoutcastRecorder.vcxproj.filters @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="Source Files"> + <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier> + <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions> + </Filter> + <Filter Include="Header Files"> + <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier> + <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions> + </Filter> + <Filter Include="Resource Files"> + <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier> + <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions> + </Filter> + <Filter Include="include"> + <UniqueIdentifier>{f6d41f57-d9af-48a6-b803-afbad02b9764}</UniqueIdentifier> + </Filter> + <Filter Include="src"> + <UniqueIdentifier>{f9dce2be-2382-4226-b11b-cca6f61584ca}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <None Include="ReadMe.txt" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="stdafx.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="targetver.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\include\curl.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\files.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\header.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\icy-string.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\log.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\metadata.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\mp3data.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\parsing.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\pls.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\shoutcast.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\stream.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="..\..\include\types.h"> + <Filter>include</Filter> + </ClInclude> + <ClInclude Include="getopt.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <ClCompile Include="stdafx.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="shoutcastRecorder.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\src\curl.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\files.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\header.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\icy-string.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\log.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\main.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\metadata.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\mp3data.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\pls.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\shoutcast.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="..\..\src\stream.c"> + <Filter>src</Filter> + </ClCompile> + <ClCompile Include="getopt.c"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> +</Project> \ No newline at end of file diff --git a/shoutcastRecorder/shoutcastRecorder/stdafx.cpp b/shoutcastRecorder/shoutcastRecorder/stdafx.cpp new file mode 100755 index 0000000..32d2274 --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// shoutcastRecorder.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/shoutcastRecorder/shoutcastRecorder/stdafx.h b/shoutcastRecorder/shoutcastRecorder/stdafx.h new file mode 100755 index 0000000..47a0d02 --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/stdafx.h @@ -0,0 +1,15 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#include <stdio.h> +#include <tchar.h> + + + +// TODO: reference additional headers your program requires here diff --git a/shoutcastRecorder/shoutcastRecorder/targetver.h b/shoutcastRecorder/shoutcastRecorder/targetver.h new file mode 100755 index 0000000..90e767b --- /dev/null +++ b/shoutcastRecorder/shoutcastRecorder/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include <SDKDDKVer.h> diff --git a/src/curl.c b/src/curl.c old mode 100644 new mode 100755 index d243e6e..623dce4 --- a/src/curl.c +++ b/src/curl.c @@ -1,42 +1,172 @@ #include <curl/curl.h> #include <curl/easy.h> - +#include <time.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> #include "types.h" #include "parsing.h" +#include "log.h" + +#if LIBCURL_VERSION_NUM >= 0x073d00 +#define TIME_IN_US 1 +#define TIMETYPE curl_off_t +#define TIMEOPT CURLINFO_TOTAL_TIME_T +#define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 1000000 +#else +#define TIMETYPE double +#define TIMEOPT CURLINFO_TOTAL_TIME +#define MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL 1 +#endif + +#ifndef CURL_MAX_READ_SIZE +#define CURL_MAX_READ_SIZE 10000000L +#endif + +struct myprogress { + TIMETYPE lastruntime; /* type depends on version, see above */ + CURL *curl; + TIMETYPE duration; + Stream *stream; + time_t last; + void (*thread) (void *); +}; + +void SwapOfs(void *p) { + struct myprogress *myp = (struct myprogress *)p; + CURL *curl = myp->curl; + Stream *stream = myp->stream; + TIMETYPE duration = stream->duration; + duration*=MINIMAL_PROGRESS_FUNCTIONALITY_INTERVAL; + time_t now; + time(&now); + TIMETYPE curtime = 0; + curl_easy_getinfo(curl, TIMEOPT, &curtime); + /* under certain circumstances it may be desirable for certain functionality + to only run every N seconds, in order to do this the transaction time can + be used */ + if( (curtime - myp->lastruntime) >= duration || difftime(now,myp->last)>= stream->duration) { + newfilename(stream, stream->stream_title); + + plog("SwapOfs %lld %lld-%08lld=%lld %ld-%ld=%ld %s\n", duration, curtime, myp->lastruntime,curtime-myp->lastruntime, now, myp->last, now-myp->last,stream->filename); + myp->lastruntime = curtime; + myp->last=now; + } +} + +#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) +{ + struct myprogress *myp = (struct myprogress *)p; + myp->thread(p); + return 0; +} +#else +/* for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION) */ +int older_progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow) +{ + struct myprogress *myp = (struct myprogress *)p; + myp->thread(p); + return 0; +} +#endif int read_stream(Stream *stream) { - CURL *curl; - CURLcode curl_res; - struct curl_slist *headers = NULL; - int ret = 0; - - if (stream->url == NULL) { - ret = -1; - goto early_err; - } - - if ((curl = curl_easy_init()) == NULL) { - ret = -1; - goto early_err; - } - - headers = curl_slist_append(headers, "Icy-MetaData:1"); // On force la récupération des metadata - - curl_easy_setopt(curl, CURLOPT_URL, stream->url); - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, parse_data); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, stream); - - if ((curl_res = curl_easy_perform(curl)) != 0) { - printf("ERROR : %s\n", curl_easy_strerror(curl_res)); - ret = -1; - goto err; - } - -err: - curl_slist_free_all(headers); - curl_easy_cleanup(curl); + struct myprogress prog; + time_t now; + struct tm *info; + char buffer[80]; + CURL *curl; + CURLcode curl_res; + struct curl_slist *headers = NULL; + int ret = 0; + if (stream->url == NULL) { + time(&now); + info = localtime( &now ); + strftime(buffer,80,"%T Error : stream->url null\n", info); + printf(buffer); + ret = -1; + goto early_err; + } + time(&now); + info = localtime( &now ); + strftime(buffer,80, "%T", info); + printf("%s stream->url [%s]\n", buffer, stream->url); + printf("%s stream->proxy [%s]\n", buffer, stream->proxy); + if ((curl = curl_easy_init()) == NULL) { + time(&now); + info = localtime( &now ); + strftime(buffer,80,"%T Error : curl_easy_init\n", info); + printf(buffer); + ret = -1; + goto early_err; + } + printf("%s libcurlversion %s\n", buffer, LIBCURL_VERSION); + prog.curl = curl; + prog.stream = stream; + prog.thread = &SwapOfs; + prog.lastruntime = 0; + time(&prog.last); + headers = curl_slist_append(headers, "Icy-MetaData:1"); // On force la récupération des metadata + curl_easy_setopt(curl, CURLOPT_URL, stream->url); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_PROXY, stream->proxy); + curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parse_header); + curl_easy_setopt(curl, CURLOPT_HEADERDATA, stream); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, parse_data); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, stream); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 60L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, CURL_MAX_READ_SIZE); + curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, 30000L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.64.0"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_2TLS); +#ifdef CURLOPT_HTTP09_ALLOWED + curl_easy_setopt(curl, CURLOPT_HTTP09_ALLOWED, 1L); +#endif + +#if LIBCURL_VERSION_NUM >= 0x072000 + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo); + /* pass the struct pointer into the xferinfo function, note that this is an alias to CURLOPT_PROGRESSDATA */ + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &prog); +#else + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, older_progress); + /* pass the struct pointer into the progress function */ + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog); +#endif + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + time_t start_t, end_t; + time(&start_t); + uint seconds_elapsed = 0; + uint duration=1+stream->duration*(1+stream->repeat);// add 1 second to enforce newfile before timeout + do { + curl_easy_setopt(curl, CURLOPT_TIMEOUT, duration-seconds_elapsed); + curl_res = curl_easy_perform(curl); + if (curl_res == CURLE_OK || curl_res == CURLE_OPERATION_TIMEDOUT || curl_res == CURLE_RECV_ERROR) { + // expected curl_res + } else { + // unexpected curl_res + time(&now); + info = localtime( &now ); + strftime(buffer,80,"%T", info); + printf("%s ERROR %2d: %s\n", buffer, curl_res, curl_easy_strerror(curl_res)); + ret = -1; +// goto err; // not error out in such a case? + sleep(60); // one minute + } + time(&end_t); + seconds_elapsed=(uint)difftime(end_t, start_t); + } while(seconds_elapsed < duration); +//err: + curl_slist_free_all(headers); + curl_easy_cleanup(curl); early_err: - return ret; + return ret; } diff --git a/src/files.c b/src/files.c index 3aa0bf3..d4d4a64 100644 --- a/src/files.c +++ b/src/files.c @@ -6,34 +6,34 @@ int get_extension(char *extension, char *string) { - char *pstr = NULL; + char *pstr = NULL; - if (string == NULL) - return -1; + if (string == NULL) + return -1; - pstr = strrchr(string, '.'); - if (pstr == NULL) { - strcpy(extension, ""); - return -1; - } + pstr = strrchr(string, '.'); + if (pstr == NULL) { + strcpy(extension, ""); + return -1; + } - strcpy(extension, pstr+1); + strcpy(extension, pstr+1); - return 0; + return 0; } int is_pls_extension(char *string) { - char extension[10]; + char extension[10]; - if (string == NULL) - return FALSE; + if (string == NULL) + return FALSE; - get_extension(extension, string); + get_extension(extension, string); - if (!strcmp(extension, "pls")) - return TRUE; - else - return FALSE; + if (!strcmp(extension, "pls")) + return TRUE; + else + return FALSE; } diff --git a/src/header.c b/src/header.c index 0443b7e..0e5c151 100644 --- a/src/header.c +++ b/src/header.c @@ -1,5 +1,6 @@ #include <stdio.h> #include <stdlib.h> +#include <limits.h> #include "types.h" #include "header.h" @@ -9,59 +10,60 @@ int header_listener(Stream *stream, char *buffer) { - if (!is_header(stream)) - return 1; + ICYHeader *header; + if (!is_header(stream)) + return 1; - ICYHeader *header = &stream->header; + header = &stream->header; - if (stream->bytes_count_total >= HEADER_MAX) { - printf("Error : couldn't retrieve server information.\n"); - exit(-1); - } + if (stream->bytes_count_total >= HEADER_MAX) { + printf("Error : couldn't retrieve server information.\n"); + exit(-1); + } - *header->ptr = *buffer; + *header->ptr = *buffer; - if (is_end_of_http_header(header)) { - extract_header_fields(header); - free(header->buffer); + if (is_end_of_http_header(header)) { + extract_header_fields(header); - if (header->metaint == 0) { - printf("Error : Couldn't find metaint information\n"); - exit(-1); - } + if (header->metaint == 0) { + printf("Error : Couldn't find metaint information\n"); + } else { + stream->status = E_STATUS_MP3DATA; + } + print_header(header); - print_header(header); - - // TODO init_for_mp3data(stream) ? - stream->bytes_count = 0; - stream->bytes_count_total = 0; // TODO : Commenter - stream->mp3data.size = 0; - stream->status = E_STATUS_MP3DATA; - } - else { - header->ptr++; - } - return 0; + // TODO init_for_mp3data(stream) ? + stream->bytes_count = 0; + stream->bytes_count_total = 0; // TODO : Commenter + stream->mp3data.size = 0; + } + else { + header->ptr++; + } + return 0; } int is_header(Stream *stream) { - if (stream->status == E_STATUS_HEADER) - return TRUE; - else - return FALSE; + if (stream->status == E_STATUS_HEADER){ + return TRUE; + + } else { + return FALSE; + } } int print_header(ICYHeader *header) { - printf("##################################\n"); - printf("Name\t: %s\n", header->icy_name); - printf("icy-notice1\t: %s\n", header->icy_notice1); - printf("icy-notice2\t: %s\n", header->icy_notice2); - printf("Genre\t: %s\n", header->icy_genre); - //printf("Public\t: %s\n", (header->icy_pub?"yes":"no")); - printf("Bitrate : %s kbit/s\n", header->icy_br); - printf("metaint\t: %d\n", header->metaint); - printf("##################################\n"); - return 0; + printf("##################################\n"); + printf("Name\t: %s\n", header->icy_name); + printf("icy-notice1\t: %s\n", header->icy_notice1); + printf("icy-notice2\t: %s\n", header->icy_notice2); + printf("Genre\t: %s\n", header->icy_genre); + //printf("Public\t: %s\n", (header->icy_pub?"yes":"no")); + printf("Bitrate : %s kbit/s\n", header->icy_br); + printf("metaint\t: %d\n", header->metaint); + printf("##################################\n"); + return 0; } diff --git a/src/icy-string.c b/src/icy-string.c index 579df22..c2242b3 100644 --- a/src/icy-string.c +++ b/src/icy-string.c @@ -2,88 +2,91 @@ #include <stdlib.h> #include <string.h> #include "types.h" - #include "icy-string.h" int extract_header_fields(ICYHeader *header) { - char metaint[20]; - get_http_header_field(header->buffer, "icy-name", header->icy_name); - get_http_header_field(header->buffer, "icy-notice1", header->icy_notice1); - get_http_header_field(header->buffer, "icy-notice2", header->icy_notice2); - get_http_header_field(header->buffer, "icy-genre", header->icy_genre); - get_http_header_field(header->buffer, "icy-pub", header->icy_pub); - get_http_header_field(header->buffer, "icy-br", header->icy_br); - get_http_header_field(header->buffer, "icy-metaint", metaint); - header->metaint = atoi(metaint); - return 0; + char metaint[20]; + get_http_header_field(header->buffer, "icy-name", header->icy_name); + get_http_header_field(header->buffer, "icy-notice1", header->icy_notice1); + get_http_header_field(header->buffer, "icy-notice2", header->icy_notice2); + get_http_header_field(header->buffer, "icy-genre", header->icy_genre); + get_http_header_field(header->buffer, "icy-pub", header->icy_pub); + get_http_header_field(header->buffer, "icy-br", header->icy_br); + if(0==get_http_header_field(header->buffer, "icy-metaint", metaint)){ + header->metaint = atoi(metaint); + } else { + header->metaint = 0; + } + return 0; } int get_http_header_field(char *header, const char* field, char* value) { - int i; - char *occurrence = NULL; - int content_pos = 0; + int i; + char *occurrence = NULL; + int content_pos = 0; - occurrence = strstr(header, field); - content_pos = strlen(field)+1; - // TODO : Test NULL value - for (i=content_pos; occurrence[i] != '\0';i++) { - if (is_cr_present(occurrence, i)) { - // "<field>:" is deleted - strncpy(value, occurrence+content_pos, i-content_pos); - value[i-content_pos-1] = '\0'; - return 0; - } - } - // Value hasn't been found - value[0] = '\0'; - return 1; + occurrence = strcasestr(header, field); + content_pos = strlen(field)+1; + if (occurrence != NULL) { + for (i=content_pos; occurrence[i] != '\0';i++) { + if (is_cr_present(occurrence, i)) { + // "<field>:" is deleted + strncpy(value, occurrence+content_pos, i-content_pos); + value[i-content_pos-1] = '\0'; + return 0; + } + } + } + // Value hasn't been found + value[0] = '\0'; + return 1; } int get_metadata_field(char *metadata, const char* field, char* value) { - char *split; - char *occurrence = NULL; - split = strtok (metadata,";"); - while (split != NULL) { - occurrence = strstr(split, field); - if (occurrence != NULL) { - unsigned int content_pos = strlen(field)+2; - unsigned int content_size = strlen(split)-content_pos-1; - strncpy(value, occurrence+content_pos, content_size); - value[content_size] = '\0'; - return 0; - } - split = strtok (NULL,";"); - } - // Value hasn't been found - value[0]='\0'; - return 1; + char *split; + char *occurrence = NULL; + split = strtok (metadata,";"); + while (split != NULL) { + occurrence = strcasestr(split, field); + if (occurrence != NULL) { + unsigned int content_pos = strlen(field)+2; + unsigned int content_size = strlen(split)-content_pos-1; + strncpy(value, occurrence+content_pos, content_size); + value[content_size] = '\0'; + return 0; + } + split = strtok (NULL,";"); + } + // Value hasn't been found + value[0]='\0'; + return 1; } int is_cr_present(char *str, int pos) { - if (str[pos-1] == '\r' && str[pos] == '\n') - return TRUE; - else - return FALSE; + if (str[pos-1] == '\r' && str[pos] == '\n') + return TRUE; + else + return FALSE; } int is_end_of_http_header(ICYHeader *header) { - unsigned int buffer_size = 0; - buffer_size = header->ptr - header->buffer + 1; + unsigned int buffer_size = 0; + buffer_size = header->ptr - header->buffer + 1; - if (buffer_size < 4) - return FALSE; + if (buffer_size < 4) + return FALSE; - if (*(header->ptr-3) == '\r' && - *(header->ptr-2) == '\n' && - *(header->ptr-1) == '\r' && - *(header->ptr) == '\n') - return TRUE; - else - return FALSE; + if (*(header->ptr-3) == '\r' && + *(header->ptr-2) == '\n' && + *(header->ptr-1) == '\r' && + *(header->ptr) == '\n') + return TRUE; + else + return FALSE; } diff --git a/src/log.c b/src/log.c index da24803..dde8c66 100644 --- a/src/log.c +++ b/src/log.c @@ -1,93 +1,115 @@ #include <stdio.h> #include <time.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <stdarg.h> #include "log.h" static FILE *fp_log; -static FILE *fp_prog; +static char current_time[25]; +static char current_date[20]; +static char tempfile[270]; +static FILE* fout = NULL; +static char buffr[20]; static int get_time(char *string) { - time_t rawtime; - struct tm *timeinfo; - - if (string == NULL) - return -1; - - time (&rawtime); - timeinfo = localtime(&rawtime); - - sprintf(string, "%d:%02d:%02d-%02d:%02d:%02d ", 1900+timeinfo->tm_year, - timeinfo->tm_mon+1, - timeinfo->tm_mday, - timeinfo->tm_hour, - timeinfo->tm_min, - timeinfo->tm_sec); - - return 0; + struct timeval curTime; + if (string == NULL) + return -1; + gettimeofday(&curTime, NULL); + int milli = curTime.tv_usec / 1000; + strftime(buffr, 20, "%Y-%m-%d %H:%M:%S", localtime(&curTime.tv_sec)); + snprintf(string, 25, "%s.%03d ", buffr, milli); + return 0; } -int log_open_files(void) +static int get_date(char *string) { - if (fp_log != NULL) - return -1; - - fp_log = fopen("shoutr.log","a"); - if (fp_log == NULL) { - printf("Couldn't open shoutr.log file\n"); - return -1; - } - - fp_prog = fopen("prog.log","a"); - if (fp_prog == NULL) { - printf("Couldn't open prog.log file\n"); - fclose(fp_log); - return -1; - } - - return 0; + struct timeval curTime; + if (string == NULL) + return -1; + gettimeofday(&curTime, NULL); + strftime(string, 20, "%a.%Y%m%d.%H%M%S", localtime(&curTime.tv_sec)); + return 0; } -int log_close_files(void) +int log_open_files(char* folder) { - if (fclose(fp_log) == EOF) - return EOF; + if (fp_log != NULL) + return -1; + + if( get_date(current_date) <0) { + printf("Couldn't get date"); + return -1; + } + struct stat st; + if (stat(folder, &st) != 0) + { + mkdir(folder, 0777); + } + snprintf(tempfile,270,"%s/shoutr.%s.log",folder,current_date); + fp_log = fopen(tempfile,"a"); + if (fp_log == NULL) { + printf("Couldn't open %s file\n", tempfile); + return -1; + } + + return 0; +} - if (fclose(fp_prog) == EOF) - return EOF; +int log_close_files(void) +{ + if (fclose(fp_log) == EOF) + return EOF; - return 0; + return 0; } static int log_append(FILE *fp, char *line) { - char current_time[20]; + if (fp == NULL || line == NULL) + return -1; - if (fp == NULL || line == NULL) - return -1; + if (get_time(current_time) < 0) + return -1; - if (get_time(current_time) < 0) - return -1; + if (fputs(current_time, fp) == EOF) + return EOF; - if (fputs(current_time, fp) == EOF) - return EOF; + if (fputs(line, fp) == EOF) + return EOF; - if (fputs(line, fp) == EOF) - return EOF; + if (fputc('\n', fp) == EOF) + return EOF; - if (fflush(fp) == EOF) - return EOF; - else - return 0; + if (fflush(fp) == EOF) + return EOF; + else + return 0; } void slog(char *line) { - log_append(fp_log, line); + if (log_append(fp_log, line) < 0) + printf("Coudln't write shoutr log\n"); + if (!fout) + fout = stdout; + log_append(fout, line); } -void slog_prog(char *line) -{ - if (log_append(fp_prog, line) < 0) - printf("Coudln't write prog log\n"); +void printCurrentTime() { + if (get_time(current_time) < 0) + return; + fwrite(current_time, sizeof(char), sizeof(current_time)-1, stdout); +} + +void plog(char *fmt, ...) { + printCurrentTime(); + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); } diff --git a/src/main.c b/src/main.c old mode 100644 new mode 100755 index 4f04021..eb3abbf --- a/src/main.c +++ b/src/main.c @@ -2,109 +2,220 @@ #include <stdio.h> #include <string.h> #include <math.h> +#include <time.h> +#ifdef _WIN32 +#include <io.h> +#include "../shoutcastRecorder/shoutcastRecorder/getopt.h" +#else #include <unistd.h> // linux #include <regex.h> - +#endif #include "types.h" #include "parsing.h" #include "shoutcast.h" #include "curl.h" #include "log.h" -int load_stream_from_playlist(char *filename); +int load_stream_from_playlist(Stream *stream, char *filename); void usage(void) { - printf("Usage: shoutr [OPTIONS]\n"); - printf(" -p\t: playlist file\n"); - printf(" -u\t: stream url\n"); + printf("Usage: shoutr [-p <playlist>|-u <stream_url>] [OPTIONS]\n"); + printf("options:\n"); + printf("\t-d\t: recording duration (in seconds)\n"); + printf("\t-e\t: fileextension (default mp3)\n"); + printf("\t-f\t: basefilename (default radio)\n"); + printf("\t-l\t: logfolder (default current folder)\n"); + printf("\t-n\t: name of station (default radio)\n"); + printf("\t-r\t: recording repeats (default 0 = none)\n"); + printf("\t-s\t: split title - artist (0) or artist - title (1, default)\n"); + printf("\t-t\t: title to record (default any)\n"); + printf("\t-x\t: proxy (default no proxy)\n"); + printf("\t-z\t: use only artist/title for filename\n"); } int main(int argc, char *argv[]) { - int ret = -1; - int pflag = 0; - int uflag = 0; - int c; - char *cvalue = NULL; - - while ((c = getopt(argc, argv, "p:u:h")) != -1) { - switch(c) { - // playlist - case 'p': - pflag = 1; - cvalue = optarg; - break; - // stream url - case 'u': - uflag = 1; - cvalue = optarg; - break; - case 'h': - default: - usage(); - goto err_early; - break; - } - } - - - if (pflag && uflag) { - usage(); - goto err_early; - } - - if ((ret = log_open_files()) < 0) { - printf("Couldn't open log files.\n"); - goto err_early; - } - - if (pflag) { - if ((ret = load_stream_from_playlist(cvalue)) < 0) { - printf("Couldn't load stream from playlist\n"); - goto err; + if (argc == 1) { + usage(); + return -1; + } + int ret = -1; + int pflag = 0; + int uflag = 0; + int c; + char *cvalue = NULL; + char *log = malloc(TITLE_SIZE*sizeof(char)); + memset(log, 0, TITLE_SIZE); + strncpy(log, ".", TITLE_SIZE-1); + Stream stream; + memset(stream.to_ignore, 0, TITLE_SIZE); + memset(stream.ext, 0, TITLE_SIZE); + strncpy(stream.ext, "mp3", TITLE_SIZE-1); + memset(stream.proxy, 0, TITLE_SIZE); + stream.duration=0; + stream.repeat=0; + stream.TA = 1; + stream.ignoring = FALSE; + memset(stream.basefilename, 0, TITLE_SIZE); + strncpy(stream.basefilename,"radio",TITLE_SIZE-1); + memset(stream.stream_title, 0, TITLE_SIZE); + memset(stream.station, 0, TITLE_SIZE); + memset(stream.onlytitle, 0, TITLE_SIZE); + + while ((c = getopt(argc, argv, "p:u:h:x:f:e:d:r:i:l:n:t:s:z:")) != -1) { + switch(c) { + // playlist + case 'p': + pflag = 1; + cvalue = optarg; + printf("playlist: %s\n", optarg); + break; + // stream url + case 'u': + uflag = 1; + cvalue = optarg; + printf("url: %s\n", optarg); + break; + // proxy + case 'x': + strncpy(stream.proxy, optarg, TITLE_SIZE-1); + printf("proxy: %s\n", optarg); + break; + // fileextension + case 'e': + strncpy(stream.ext, optarg, TITLE_SIZE-1); + printf("ext: %s\n", optarg); + break; + // basefilename + case 'f': + strncpy(stream.basefilename, optarg, TITLE_SIZE-1); + printf("basefilename: %s\n", optarg); + break; + // duration + case 'd': + stream.duration = atoi(optarg); + printf("duration: %s\n", optarg); + break; + case 'r': + stream.repeat=atoi(optarg); + printf("repeat: %s\n", optarg); + break; + case 's': + if (atoi(optarg)> 0){ + stream.TA |= 1; + } else { + stream.TA &= ~1; } - } - - if (uflag) { - Stream stream; - load_stream(&stream, cvalue); - - if ((ret = read_stream(&stream)) < 0) { - printf("Error : Couldn't read Shoutcast stream\n"); - goto err; + printf("TA: %s TA:%i\n", optarg, stream.TA); + break; + case 'z': + if (atoi(optarg)> 0){ + stream.TA |= 2; + } else { + stream.TA &= ~2; } - } - - // res = load_stream(&stream, "http://88.190.24.47:80"); + printf("onlyta: %s TA:%i\n", optarg, stream.TA); + break; + case 'l': + strncpy(log, optarg, TITLE_SIZE-1); + printf("log: %s\n", optarg); + break; + case 'n': + strncpy(stream.stream_title, optarg, TITLE_SIZE-1); + strncpy(stream.station, optarg, TITLE_SIZE-1); + printf("station: %s\n", optarg); + break; + case 't': + strncpy(stream.onlytitle, optarg, TITLE_SIZE-1); + printf("onlytitle: %s\n", optarg); + break; + case 'i': + strncpy(stream.to_ignore, optarg, TITLE_SIZE-1); + printf("to_ignore: %s\n", optarg); + break; + case 'h': + default: + usage(); + goto err_early; + break; + } + } + if (pflag && uflag) { + usage(); + goto err_early; + } + if ((ret = log_open_files(log)) < 0) { + printf("Couldn't open log files.\n"); + goto err_early; + } + if (pflag) { + if ((ret = load_stream_from_playlist(&stream, cvalue)) < 0) { + printf("Couldn't load stream from playlist\n"); + goto err; + } + } + + if (uflag) { + load_stream(&stream, cvalue); + } + + if ((ret = read_stream(&stream)) < 0) { + printf("Error : Couldn't read Shoutcast stream\n"); + goto err; + } err: - log_close_files(); + log_close_files(); err_early: - return ret; + free(log); + return ret; +} + +size_t parse_header(void *ptr, size_t size, size_t nmemb, void *userdata) +{ + unsigned int i; + char buffer; + Stream *stream = (Stream *)userdata; + size_t numbytes = size * nmemb; + + //stream->header.buffer; + //copy *ptr to header.buffer starting at header.ptr position + void* dest = stream->header.ptr; + memcpy(dest, ptr, numbytes); + + for (i=0;i<numbytes;i++) { + buffer = ((char*)ptr)[i]; + global_listener(stream, &buffer); + stream->bytes_count_total++; + } + + printf("%.*s", (int) numbytes, (char*)ptr); + return numbytes; } size_t parse_data(void *ptr, size_t size, size_t nmemb, void *userdata) { - unsigned int i; - char buffer; - Stream *stream = (Stream *)userdata; + unsigned int i; + char buffer; + Stream *stream = (Stream *)userdata; + size_t numbytes = size * nmemb; - stream->mp3data.buffer = (char*) malloc(nmemb); - stream->mp3data.ptr = stream->mp3data.buffer; + stream->mp3data.buffer = (char*) malloc(numbytes); + stream->mp3data.ptr = stream->mp3data.buffer; - for (i=0;i<nmemb;i++) { - buffer = ((char*)ptr)[i]; - global_listener(stream, &buffer); - stream->bytes_count_total++; - } + for (i=0;i<numbytes;i++) { + buffer = ((char*)ptr)[i]; + global_listener(stream, &buffer); + stream->bytes_count_total++; + } - write_data(stream, &size); - free(stream->mp3data.buffer); - stream->mp3data.size = 0; + write_data(stream); + free(stream->mp3data.buffer); + stream->mp3data.size = 0; - stream->blocks_count++; + stream->blocks_count++; - return nmemb; + return numbytes; } diff --git a/src/metadata.c b/src/metadata.c old mode 100644 new mode 100755 index d5b57be..a3e0625 --- a/src/metadata.c +++ b/src/metadata.c @@ -1,6 +1,8 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/param.h> +#include <ctype.h> #include "types.h" #include "metadata.h" @@ -8,88 +10,170 @@ #include "log.h" +static char ascii[256]={'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +' ','!','_','#','$','%','&','\'','(',')','_','+',',','-','.','_', +'0','1','2','3','4','5','6','7','8','9','_',';','_','=','_','_', +'@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O', +'P','Q','R','S','T','U','V','W','X','Y','Z','[','_',']','^','_', +'`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', +'p','q','r','s','t','u','v','w','x','y','z','{','_','}','~',' ', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'A','A','A','A','A','A','E','C','E','E','E','E','I','I','I','I', +'D','N','O','O','O','O','O','x','O','U','U','U','U','Y','p','B', +'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', +'o','o','o','o','o','o','o','+','o','u','u','u','u','y','p','y'}; + + int metadata_listener(Stream *stream, char *buffer) { - if (!is_metadata(stream)) - return -1; + if (!is_metadata(stream)) + return -1; - if (is_metadata_header(stream)) - return metadata_header_handler(stream, buffer); - else if (is_metadata_body(stream)) - return metadata_body_handler(stream, buffer); + if (is_metadata_header(stream)) + return metadata_header_handler(stream, buffer); + else if (is_metadata_body(stream)) + return metadata_body_handler(stream, buffer); - return 0; + return 0; } int metadata_header_handler(Stream *stream, char *buffer) { - MetaData *metadata = &stream->metadata; + MetaData *metadata = &stream->metadata; + + metadata->ptr = metadata->buffer; // Rewind + metadata->size = abs((int)*buffer) * 16; - metadata->ptr = metadata->buffer; // Rewind - metadata->size = abs((int)*buffer) * 16; + if (metadata->size == 0) { + stream->bytes_count = 0; + stream->status = E_STATUS_MP3DATA; + } + else { + stream->status = E_STATUS_METADATA_BODY; + } + + return 0; +} - if (metadata->size == 0) { - stream->bytes_count = 0; - stream->status = E_STATUS_MP3DATA; - } - else { - stream->status = E_STATUS_METADATA_BODY; - } +void removechar( char str[], unsigned int i ) +{ + for (unsigned int j=i; j<strlen(str)-2; j++) + str[j]=str[j+1]; + str[strlen(str)-1]='\0'; +} - return 0; +void rtrim(char *str) +{ + const char *seps = "\t\n\v\f\r "; + int i = strlen(str) - 1; + while (i >= 0 && strchr(seps, str[i]) != NULL) { + str[i] = '\0'; + i--; + } } +void trim(char *str) +{ + const char *seps = "\t\n\v\f\r "; + int i = 0; + while (strchr(seps, str[i]) != NULL) { + str++; + } + rtrim(str); +} int metadata_body_handler(Stream *stream, char *buffer) { - MetaData *metadata = &stream->metadata; - *metadata->ptr = *buffer; - if ((unsigned)(metadata->ptr - metadata->buffer) == (metadata->size-1)) { - char metadata_content[500]; - char stream_title[500]; - strncpy(metadata_content, metadata->buffer, metadata->size); - get_metadata_field(metadata_content, "StreamTitle", stream->stream_title); - sprintf(stream_title, "%s\n", stream->stream_title); - printf("%s", stream_title); - slog_prog(stream_title); - stream->metadata_count++; - - { - char new_filename[255] = ""; - fclose(stream->output_stream); - sprintf(new_filename, "radio%d.mp3", stream->metadata_count); - stream->output_stream = fopen(new_filename, "w"); - - } - - stream->bytes_count = 0; - stream->status = E_STATUS_MP3DATA; - } else { - metadata->ptr++; - } - return 0; + MetaData *metadata = &stream->metadata; + *metadata->ptr = *buffer; + if ((unsigned)(metadata->ptr - metadata->buffer) == (metadata->size-1)) { + char metadata_content[TITLE_SIZE]; + memset(metadata_content, 0, TITLE_SIZE); + char stream_title[TITLE_SIZE]; + memset(stream_title, 0, TITLE_SIZE); + strncpy(metadata_content, metadata->buffer, MIN(metadata->size,TITLE_SIZE)); + if(0==get_metadata_field(metadata_content, "StreamTitle", stream_title)) + { + stream_title[TITLE_SIZE-1]='\0'; + metadata_content[TITLE_SIZE-1]='\0'; + // filter problematic characters from StreamTitle + // and make PascalCase + int toUpperCase = 1; //first character toUpperCase + for (unsigned int i=0; i < MIN(metadata->size,TITLE_SIZE); i++) { + if (stream_title[i]=='\0') { break;} //done + char c=ascii[(int)stream_title[i]]; + if (toUpperCase==1) { + stream_title[i]=toupper(c); + toUpperCase=0; + } else { + stream_title[i]=tolower(c); + } + if ((' '==stream_title[i])||('.'==stream_title[i])||('('==stream_title[i])){ + toUpperCase=1; //next character toUpperCase + } + } + trim(stream_title); + if (stream_title==NULL||strlen(stream_title)==0) { + strncpy(stream_title, stream->station, TITLE_SIZE); + } + plog("stream_title: [%s] [%s] (%s)\n", stream_title, stream->stream_title,stream->to_ignore); + if (0 != strncmp(stream->stream_title, stream_title, TITLE_SIZE)) + { + if ( (NULL!=stream->to_ignore)&&(strlen(stream->to_ignore)!=0) + &&(NULL!=strstr(stream_title, stream->to_ignore))) { + plog("no newfilename, ignore [%s] found in [%s]\n", stream->to_ignore, stream_title); + stream->ignoring=TRUE; + } else { + newfilename(stream, stream_title); + } + } else { + if ( stream->ignoring == TRUE ) { + plog("no newfilename, ignore sequence [%s] active, keep same filename\n", stream->to_ignore); + stream->ignoring=FALSE; + } else if ((NULL!=stream->to_ignore) && (0 == strncmp("TRUE", stream->to_ignore, 4))) { + plog("no newfilename, ignore set to [%s]\n", stream->to_ignore); + } else { + newfilename(stream, stream_title); + } + } + } + // slog metadata_content + slog(metadata_content); + + stream->bytes_count = 0; + stream->status = E_STATUS_MP3DATA; + } else { + metadata->ptr++; + } + return 0; } int is_metadata(Stream *stream) { - if (is_metadata_body(stream) || is_metadata_header(stream)) - return TRUE; - else - return FALSE; + if (is_metadata_body(stream) || is_metadata_header(stream)) + return TRUE; + else + return FALSE; } int is_metadata_body(Stream *stream) { - if (stream->status == E_STATUS_METADATA_BODY) - return TRUE; - else - return FALSE; + if (stream->status == E_STATUS_METADATA_BODY){ + return TRUE; + } else { + return FALSE; + } } int is_metadata_header(Stream *stream) { - if (stream->status == E_STATUS_METADATA_HEADER) - return TRUE; - else - return FALSE; + if (stream->status == E_STATUS_METADATA_HEADER){ + return TRUE; + } else { + return FALSE; + } } diff --git a/src/mp3data.c b/src/mp3data.c index 8d94626..7e1dbfc 100644 --- a/src/mp3data.c +++ b/src/mp3data.c @@ -8,26 +8,29 @@ int mp3data_listener(Stream *stream, char *buffer) { - if (!is_mp3data(stream)) - return -1; + Mp3Data *mp3data; + if (!is_mp3data(stream)) + return -1; - Mp3Data *mp3data = &stream->mp3data; + mp3data = &stream->mp3data; - *mp3data->ptr = *buffer; - mp3data->size++; - mp3data->ptr++; - stream->bytes_count++; + if (mp3data->ptr == NULL) + return 0; + *mp3data->ptr = *buffer; + mp3data->size++; + mp3data->ptr++; + stream->bytes_count++; - if (stream->bytes_count == stream->header.metaint) - stream->status = E_STATUS_METADATA_HEADER; + if (stream->header.metaint > 0 && stream->bytes_count == stream->header.metaint) + stream->status = E_STATUS_METADATA_HEADER; - return 0; + return 0; } int is_mp3data(Stream *stream) { - if (stream->status == E_STATUS_MP3DATA) - return TRUE; - else - return FALSE; + if (stream->status == E_STATUS_MP3DATA) + return TRUE; + else + return FALSE; } diff --git a/src/pls.c b/src/pls.c old mode 100644 new mode 100755 index 144970e..49b525d --- a/src/pls.c +++ b/src/pls.c @@ -11,136 +11,135 @@ static int pls_get_entries(FILE *fp, PlsFile *pls); void print_pls(PlsFile *pls) { - unsigned int i; - PlsEntry *entry = pls->entries; - for (i=0;i<pls->number_entries;i++) { - printf("[%2d] %s - %s\n", i, entry->file, entry->title); - entry++; - } + unsigned int i; + PlsEntry *entry = pls->entries; + for (i=0;i<pls->number_entries;i++) { + printf("%2d %s\n", i, entry->file); + entry++; + } } int pls_load_file(char *filename, PlsFile *pls) { - unsigned int number_entries = 0; - //int res = 0; - FILE *fp; + unsigned int number_entries = 0; + //int res = 0; + FILE *fp; - fp = fopen(filename, "r"); + fp = fopen(filename, "r"); - if (fp == NULL) { - return -1; + if (fp == NULL) { + printf("fopen(%s) failed\n", filename); + return -1; } - if (!is_pls_file(fp)) { - return -1; - } + if (!is_pls_file(fp)) { + printf("%s is not a pls file\n", filename); + return -1; + } - number_entries = pls_get_number_entries(fp); - init_pls_struct(pls, number_entries); - pls_get_entries(fp, pls); + number_entries = pls_get_number_entries(fp); + init_pls_struct(pls, number_entries); + pls_get_entries(fp, pls); - printf("number_entries = %d\n", number_entries); + printf("number_entries = %d\n", number_entries); - print_pls(pls); - fclose(fp); + print_pls(pls); + fclose(fp); - return 0; + return 0; } int init_pls_struct(PlsFile *pls, unsigned int number_entries) { - pls->number_entries = number_entries; - pls->entries = malloc(number_entries*sizeof(PlsEntry)); - pls->version = 0; - if (pls->entries == NULL) { - return -1; - } else { - unsigned int i; - PlsEntry *entry = pls->entries; - for (i=0;i<pls->number_entries;i++) { - strcpy(entry->file, "\0"); - strcpy(entry->title, "\0"); - entry++; - } - return 0; - } + pls->number_entries = number_entries; + pls->entries = malloc(number_entries*sizeof(PlsEntry)); + pls->version = 0; + if (pls->entries == NULL) { + printf("no entries\n"); + return -1; + } else { + unsigned int i; + PlsEntry *entry = pls->entries; + for (i=0;i<pls->number_entries;i++) { + memset(entry->file, 0, sizeof entry->file); + memset(entry->title, 0, sizeof entry->file); + entry++; + } + return 0; + } } static int pls_get_field(char *buffer, char *value) { - char *ptr_begin; - char *ptr_end; + char *ptr_begin; + char *ptr_end; - ptr_begin = strstr(buffer, "=")+1; - ptr_end = strstr(buffer, "\n"); - strncpy(value, ptr_begin, (int)(ptr_end - ptr_begin)); + ptr_begin = strstr(buffer, "=")+1; + ptr_end = strstr(buffer, "\n")-1; + strncpy(value, ptr_begin, (int)(ptr_end - ptr_begin + 1)); - return 0; + return 0; } static int pls_get_entries(FILE *fp, PlsFile *pls) { - unsigned int i; - char buffer[MAX_LINE_LENGTH]; - PlsEntry *entry = pls->entries; - - fseek(fp, 0 ,SEEK_SET); - fgets(buffer, MAX_LINE_LENGTH, fp); - fgets(buffer, MAX_LINE_LENGTH, fp); - - for (i=0;i<pls->number_entries;i++) { - fgets(buffer, MAX_LINE_LENGTH, fp); - // TODO : gérer le NULL - - pls_get_field(buffer, entry->file); - - fgets(buffer, MAX_LINE_LENGTH, fp); - pls_get_field(buffer, entry->title); - - fgets(buffer, MAX_LINE_LENGTH, fp); - // strcpy(entry->length, buffer); - entry++; - } - return 0; // TODO : use a better return value + unsigned int i; + char buffer[MAX_LINE_LENGTH]; + PlsEntry *entry = pls->entries; + + fseek(fp, 0 ,SEEK_SET); + + i=0; + while (i<pls->number_entries) { + fgets(buffer, MAX_LINE_LENGTH, fp); + if (strstr(buffer, "File") != NULL) { + pls_get_field(buffer, entry->file); + entry++; + i++; + } + } + return 0; // TODO : use a better return value } int is_pls_file(FILE *fp) { - char buffer[MAX_LINE_LENGTH]; - - fgets(buffer, MAX_LINE_LENGTH, fp); - if (strcmp(buffer, "[playlist]\n") != 0) { - fseek(fp, 0 ,SEEK_SET); - return FALSE; - } - - // TODO : Check NumberOfEntries - // TODO : Check goup of 3 entries - - while (!feof (fp)) { - fgets(buffer, MAX_LINE_LENGTH, fp); - } - if (strstr(buffer, "Version=") != NULL) { - fseek(fp, 0, SEEK_SET); - return TRUE; - } - - return FALSE; + char buffer[MAX_LINE_LENGTH]; + fseek(fp, 0 ,SEEK_SET); + fgets(buffer, MAX_LINE_LENGTH, fp); + + if (strncmp(buffer, "[playlist]", 10) != 0) { + fseek(fp, 0 ,SEEK_SET); + printf("no [playlist] in %s\n", buffer); + return FALSE; + } + + // TODO : Check NumberOfEntries + // TODO : Check goup of 3 entries + + while (!feof (fp)) { + fgets(buffer, MAX_LINE_LENGTH, fp); + } + if (strstr(buffer, "Version=") != NULL) { + fseek(fp, 0, SEEK_SET); + return TRUE; + } + printf("no [Version=]\n"); + return FALSE; } static unsigned int pls_get_number_entries(FILE *fp) { - char buffer[MAX_LINE_LENGTH]; - char number_entries_str[3]; - - fseek(fp, 0, SEEK_SET); - fgets(buffer, MAX_LINE_LENGTH, fp); - fgets(buffer, MAX_LINE_LENGTH, fp); - - if (strstr(buffer, "NumberOfEntries=") != NULL) { - sprintf(number_entries_str, "%c%c", buffer[16], buffer[17]); - return (unsigned int)atoi(number_entries_str); - } else { - return 0; - } + char buffer[MAX_LINE_LENGTH]; + char number_entries_str[3]; + + fseek(fp, 0, SEEK_SET); + fgets(buffer, MAX_LINE_LENGTH, fp); + while (!feof (fp)) { + fgets(buffer, MAX_LINE_LENGTH, fp); + if (strstr(buffer, "NumberOfEntries=") != NULL) { + sprintf(number_entries_str, "%c%c", buffer[16], buffer[17]); + return (unsigned int)atoi(number_entries_str); + } + } + return 0; } diff --git a/src/shoutcast.c b/src/shoutcast.c index 0890f52..cfae69c 100644 --- a/src/shoutcast.c +++ b/src/shoutcast.c @@ -11,20 +11,23 @@ void global_listener(Stream *stream, char *buffer) { - // http_code_listener(stream, buffer); + // http_code_listener(stream, buffer); - if (is_header(stream)) - header_listener(stream, buffer); - else if (is_metadata(stream)) - metadata_listener(stream, buffer); - else if (is_mp3data(stream)) - mp3data_listener(stream, buffer); + if (is_header(stream)) + header_listener(stream, buffer); + else if (is_metadata(stream)) + metadata_listener(stream, buffer); + else if (is_mp3data(stream)) + mp3data_listener(stream, buffer); } -int write_data(Stream *stream, size_t *size) +int write_data(Stream *stream) { - int written = fwrite(stream->mp3data.buffer, *size, - stream->mp3data.size, - (FILE *)stream->output_stream); - return written; + if (stream->output_stream == NULL) { + return 0; + } + int written = fwrite(stream->mp3data.buffer, sizeof(char), + stream->mp3data.size, + (FILE *)stream->output_stream); + return written; } diff --git a/src/stream.c b/src/stream.c old mode 100644 new mode 100755 index 93fbdb3..1e3004d --- a/src/stream.c +++ b/src/stream.c @@ -1,86 +1,256 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> - +#include <time.h> #include "types.h" #include "stream.h" #include "files.h" #include "pls.h" #include "curl.h" +#include "log.h" +#include <taglib/tag_c.h> +#include <glob.h> +#include <regex.h> +void free_stream(Stream *stream) +{ + plog("free_stream\n"); + ICYHeader *header = &stream->header; + free(header->buffer); + header->ptr= NULL; +} int load_stream(Stream *stream, const char *url) { - ICYHeader *header = &stream->header; - MetaData *metadata = &stream->metadata; - Mp3Data *mp3data = &stream->mp3data; - - // Setting header - header->icy_name[0] = '\0'; - header->icy_notice1[0] = '\0'; - header->icy_notice2[0] = '\0'; - header->icy_genre[0] = '\0'; - header->icy_pub[0] = '\0'; - header->icy_br[0] = '\0'; - header->is_set = 0; - header->buffer = malloc(4000*sizeof(char)); - header->ptr = header->buffer; - header->metaint = 0; - - metadata->ptr = NULL; - metadata->size = 0; - - mp3data->ptr = NULL; - - // Setting Stream information - stream->bytes_count = 0; - stream->bytes_count_total = 0; - stream->blocks_count = 0; - stream->metadata_count = 0; - stream->stream_title[0] = '\0'; - - stream->status = E_STATUS_HEADER; - - stream->output_stream = fopen("radio0.mp3","w"); - if (strcpy(stream->url, url) != NULL) - return 0; - else - return 1; + ICYHeader *header = &stream->header; + MetaData *metadata = &stream->metadata; + Mp3Data *mp3data = &stream->mp3data; + + // Setting header + header->icy_name[0] = '\0'; + header->icy_notice1[0] = '\0'; + header->icy_notice2[0] = '\0'; + header->icy_genre[0] = '\0'; + header->icy_pub[0] = '\0'; + header->icy_br[0] = '\0'; + header->is_set = 0; + header->buffer = malloc(4000*sizeof(char)); + header->ptr = header->buffer; + header->metaint = 0; + + metadata->ptr = NULL; + metadata->size = 0; + + mp3data->ptr = NULL; + + // Setting Stream information + stream->bytes_count = 0; + stream->bytes_count_total = 0; + stream->blocks_count = 0; + stream->metadata_count = 0; + stream->output_stream = NULL; + stream->filename[0] = '\0'; + + stream->status = E_STATUS_HEADER; + + strncpy(stream->url, url, TITLE_SIZE); + newfilename(stream, stream->stream_title); + return 0; } -int load_stream_from_playlist(char *filename) +int load_stream_from_playlist(Stream *stream, char *filename) { - Stream stream; - PlsFile pls; - int ret = 0; - - if (filename == NULL) { - ret = -1; - goto early_err; - } - - if (!is_pls_extension(filename)) { - ret = -1; - goto early_err; - } - - if ((ret = pls_load_file(filename, &pls)) < 0) { - printf("Error : Couldn't load pls file\n"); - goto early_err; - } - - if ((ret = load_stream(&stream, pls.entries->file)) < 0) { - printf("Error : Couldn't load Shoutcast stream\n"); - goto err; - } - - if ((ret = read_stream(&stream)) < 0) { - printf("Error : Couldn't read Shoutcast stream\n"); - goto err; - } + PlsFile pls; + int ret = 0; + + if (filename == NULL) { + ret = -1; + goto early_err; + } + + if (!is_pls_extension(filename)) { + ret = -1; + goto early_err; + } + + if ((ret = pls_load_file(filename, &pls)) < 0) { + printf("Error : Couldn't load pls file\n"); + goto early_err; + } + + if ((ret = load_stream(stream, pls.entries->file)) < 0) { + printf("Error : Couldn't load Shoutcast stream\n"); + goto err; + } err: - free(pls.entries); + free(pls.entries); early_err: - return ret; + return ret; + +} +static char tolower[256]={ +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +' ','!','_','#','$','%','&','\'','(',')','_','+',',','-','.','_', +'0','1','2','3','4','5','6','7','8','9','_',';','_','=','_','_', +'@','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', +'p','q','r','s','t','u','v','w','x','y','z','[','_',']','^',' ', +'`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', +'p','q','r','s','t','u','v','w','x','y','z','{','_','}','~',' ', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'_','_','_','_','_','_','_','_','_','_','_','_','_','_','_','_', +'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', +'o','n','o','o','o','o','o','x','o','u','u','u','u','y','p','b', +'a','a','a','a','a','a','a','c','e','e','e','e','i','i','i','i', +'o','o','o','o','o','o','o','+','o','u','u','u','u','y','p','y'}; + +char* stristr(const char* haystack, const char* needle) { + do { + const char* h = haystack; + const char* n = needle; + while (tolower[(int)(*h)] == tolower[(int)(*n)] && *n) { + h++; + n++; + } + if (*n == 0) { + return (char *) haystack; + } + } while (*haystack++); + return 0; +} + +int exists(const char *fname) +{ + FILE *file; + if ((file = fopen(fname, "r"))) + { + fclose(file); + return 1; + } + return 0; } + +int exists_partially(char * fname) { + glob_t result; + int exists = 0; + const int ok = glob( fname, /*flags:*/ 0, /*errfunc:*/ NULL, &result ); + if( 0 == ok ) { + exists = result.gl_pathc; + } + else { + // error + } + return exists; +} + +void newfilename(Stream* stream, const char* title) +{ + const int size=TITLE_SIZE+1+3+1+TITLE_SIZE+1+TITLE_SIZE; + char filename[size]; + time_t rawtime; + struct tm * timeinfo; + time (&rawtime); + timeinfo = localtime(&rawtime); + char basefilename[TITLE_SIZE]; + strftime(basefilename,TITLE_SIZE-1,stream->basefilename,timeinfo); + + snprintf(filename,size,"%s.%02d*", basefilename, stream->metadata_count); + while (0 != exists_partially(filename)) { + stream->metadata_count++; + snprintf(filename,size,"%s.%02d*", basefilename, stream->metadata_count); + } + if (title==NULL||strlen(title)==0) { + snprintf(filename,size,"%s.%02d.%s", basefilename, stream->metadata_count, stream->ext); + } else { + if (stream->TA > 1) { + snprintf(filename,size,"%s%s.%s", basefilename, title, stream->ext); + } else { + snprintf(filename,size,"%s.%02d.%s.%s", basefilename, stream->metadata_count, title, stream->ext); + } + } + if (title==NULL||strlen(title)==0) { + // don't search for title match if no title to match + } else { + if (stream->onlytitle!=NULL&&strlen(stream->onlytitle)!=0) { + char str[TITLE_SIZE]; + strncpy(str, stream->onlytitle, TITLE_SIZE); + int title_found = 0; + char* token=strtok(str,","); + if (token) { + while (token) { + if(stristr(title, token)!=NULL) { + title_found = 1; + } + token=strtok(NULL,","); + } + } else { + if(stristr(title, stream->onlytitle)!=NULL) { + title_found = 1; + } + } + if (title_found == 0) { + snprintf(filename,size,"%s","/dev/null"); + } else { + stream->metadata_count++; + } + } else { + stream->metadata_count++; + } + } + filename[TITLE_SIZE-1]='\0'; + + char ext[3]; + strncpy(ext, stream->ext, 3); + char oldfilename[TITLE_SIZE]; + char oldtitle[TITLE_SIZE]; + strncpy(oldfilename,stream->filename, TITLE_SIZE); + strncpy(oldtitle,stream->stream_title, TITLE_SIZE); + + if (stream->output_stream != NULL) fclose(stream->output_stream); + stream->output_stream = fopen(filename, "wb"); + strncpy(stream->filename, filename, TITLE_SIZE); + if (title==NULL||strlen(title)==0) { + stream->stream_title[0]='\0'; + } else { + strncpy(stream->stream_title, title, TITLE_SIZE); + } + + taglib_set_strings_unicode(FALSE); + TagLib_File *media_file; + if (strncmp(ext,"aac",3) == 0) { + media_file = taglib_file_new_type(oldfilename, TagLib_File_MP4); + } else { + media_file = taglib_file_new(oldfilename); + } + if (media_file != NULL) { + TagLib_Tag *tag = taglib_file_tag(media_file); + if (tag != NULL) { + taglib_tag_set_comment(tag, oldtitle); + char * const sep_at = strstr(oldtitle, " - "); + if (sep_at != NULL) { + *sep_at='\0'; + char* title; + char* artist; + if (stream->TA == 0) { + title = oldtitle; + artist = sep_at+3; + } else { + artist = oldtitle; + title = sep_at+3; + } + taglib_tag_set_title(tag,title); + taglib_tag_set_album(tag,title); + taglib_tag_set_artist(tag,artist); + } + taglib_file_save(media_file); + } + taglib_tag_free_strings(); + taglib_file_free(media_file); + } + + plog("newfilename: %s\n", filename); +} +