mirror of
https://github.com/Team-Resurgent/XBMC4Xbox.git
synced 2026-08-15 11:18:13 -07:00
If ever adding the XBMC4Xbox source to GitHub use "git add -f -A" or it wont add all the files.
514 lines
15 KiB
C++
514 lines
15 KiB
C++
/*
|
|
* Copyright (C) 2005-2013 Team XBMC
|
|
* http://xbmc.org
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with XBMC; see the file COPYING. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "ShoutcastRipFile.h"
|
|
#include "music/tags/Id3Tag.h"
|
|
#include "settings/GUISettings.h"
|
|
|
|
using namespace MUSIC_INFO;
|
|
|
|
#define MAX_RECORDED_TRACKS 999
|
|
|
|
#if !defined(WIN32)
|
|
#define WIN32 1
|
|
#endif
|
|
|
|
#ifdef _WIN32PC
|
|
extern "C" FILE *fopen_utf8(const char *_Filename, const char *_Mode);
|
|
#else
|
|
#define fopen_utf8 fopen
|
|
#endif
|
|
|
|
#include "lib/libshout/rip_manager.h"
|
|
|
|
CShoutcastRipFile::CShoutcastRipFile()
|
|
{
|
|
m_recState.bRecording = false;
|
|
m_recState.bCanRecord = false;
|
|
m_recState.bTrackChanged = false;
|
|
m_recState.bFilenameSet = false;
|
|
m_recState.bStreamSet = false;
|
|
m_iTrackCount = 1;
|
|
m_szFileName[0] = '\0';
|
|
m_szFilteredFileName[0] = '\0';
|
|
m_szStreamName[0] = '\0';
|
|
m_ripFile = NULL;
|
|
m_logFile = NULL;
|
|
}
|
|
|
|
CShoutcastRipFile::~CShoutcastRipFile()
|
|
{
|
|
m_recState.bRecording = false;
|
|
m_recState.bCanRecord = false;
|
|
m_recState.bTrackChanged = false;
|
|
m_recState.bFilenameSet = false;
|
|
m_recState.bStreamSet = false;
|
|
m_iTrackCount = 1;
|
|
m_szFileName[0] = '\0';
|
|
m_szFilteredFileName[0] = '\0';
|
|
m_szStreamName[0] = '\0';
|
|
if ( m_ripFile != NULL )
|
|
{
|
|
fclose( m_ripFile );
|
|
m_ripFile = NULL;
|
|
}
|
|
if ( m_logFile != NULL )
|
|
{
|
|
fclose( m_logFile );
|
|
m_logFile = NULL;
|
|
}
|
|
}
|
|
|
|
void CShoutcastRipFile::Reset()
|
|
{
|
|
m_recState.bRecording = false;
|
|
m_recState.bCanRecord = false;
|
|
m_recState.bTrackChanged = false;
|
|
m_recState.bFilenameSet = false;
|
|
m_recState.bStreamSet = false;
|
|
m_iTrackCount = 1;
|
|
m_szFileName[0] = '\0';
|
|
m_szFilteredFileName[0] = '\0';
|
|
m_szStreamName[0] = '\0';
|
|
if ( m_ripFile != NULL )
|
|
{
|
|
fclose( m_ripFile );
|
|
m_ripFile = NULL;
|
|
}
|
|
if ( m_logFile != NULL )
|
|
{
|
|
fclose( m_logFile );
|
|
m_logFile = NULL;
|
|
}
|
|
}
|
|
|
|
//SetRipManagerInfo() will be called frequently, depending on meta size
|
|
void CShoutcastRipFile::SetRipManagerInfo( const RIP_MANAGER_INFO* ripInfo )
|
|
{
|
|
RIP_MANAGER_INFO info;
|
|
memcpy(&info, ripInfo, sizeof(info));
|
|
if ( m_recState.bFilenameSet && m_recState.bStreamSet ) //wait for RM to give all info needed
|
|
{
|
|
//we got everything we need
|
|
//look if we have some metainformation (change of trackname)
|
|
//RM returns Trackname like StreamName, if there is no Meta-Info
|
|
//(or maybe he is not capable sometimes?!?)
|
|
if ( strcmp( m_szFileName, m_szStreamName ) != 0 )
|
|
m_recState.bHasMetaData = true;
|
|
else
|
|
m_recState.bHasMetaData = false;
|
|
m_recState.bCanRecord = true; //now we are ready for recording...
|
|
}
|
|
else
|
|
{
|
|
//put relevant data to members
|
|
if ( !m_recState.bFilenameSet ) //check for filename
|
|
{
|
|
strcpy( m_szFileName, info.filename );
|
|
if ( m_szFileName[0] != '\0' ) //recheck, just to be sure...
|
|
{
|
|
m_recState.bFilenameSet = true;
|
|
}
|
|
}
|
|
if ( !m_recState.bStreamSet ) //and for stream name
|
|
{
|
|
strcpy( m_szStreamName, info.streamname );
|
|
if ( m_szStreamName[0] != '\0' ) //recheck, just to be sure...
|
|
{
|
|
m_recState.bStreamSet = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CShoutcastRipFile::Record()
|
|
{
|
|
//open file for logging now
|
|
if ( m_logFile == NULL ) //will be done only the first time or if path not set!
|
|
{
|
|
char logFilename[1024];
|
|
CStdString strHomePath = g_guiSettings.GetString("audiocds.recordingpath");
|
|
sprintf(logFilename, "%s\\recordings.log", strHomePath.c_str() );
|
|
m_logFile = fopen_utf8( logFilename, "at+");
|
|
}
|
|
|
|
PrepareRecording();
|
|
m_ripFile = fopen_utf8( m_szFilteredFileName, "wb+" );
|
|
|
|
char logRecording[2048];
|
|
//we log this, for users that want to change id3 tags afterward...
|
|
sprintf(logRecording, "%s ( StreamName: %s ) ( TrackName: %s )\n",
|
|
m_szFilteredFileName,
|
|
m_szStreamName,
|
|
m_szFileName );
|
|
if ( m_logFile != NULL )
|
|
{
|
|
fwrite(logRecording, strlen(logRecording), 1, m_logFile);
|
|
fflush( m_logFile ); //Flush, if file isn't proberly closed afterwards (when user turns off xbox)
|
|
}
|
|
// XTRACE(logRecording);
|
|
if ( m_ripFile != NULL )
|
|
{
|
|
m_recState.bRecording = true;
|
|
}
|
|
else
|
|
{
|
|
m_recState.bRecording = false;
|
|
}
|
|
|
|
return m_recState.bRecording;
|
|
}
|
|
|
|
bool CShoutcastRipFile::CanRecord()
|
|
{
|
|
|
|
return m_recState.bCanRecord;
|
|
}
|
|
|
|
void CShoutcastRipFile::StopRecording()
|
|
{
|
|
m_recState.bRecording = false; //stop writing, so we can write ID3 info
|
|
if (m_ripFile)
|
|
{
|
|
fclose(m_ripFile);
|
|
m_ripFile = NULL;
|
|
// Write collected ID3 Data to file
|
|
CID3Tag id3tag;
|
|
id3tag.SetMusicInfoTag(m_Tag);
|
|
id3tag.Write(m_szFilteredFileName);
|
|
}
|
|
}
|
|
|
|
bool CShoutcastRipFile::IsRecording()
|
|
{
|
|
return m_recState.bRecording;
|
|
}
|
|
|
|
void CShoutcastRipFile::Write( char *buf, unsigned long size )
|
|
{
|
|
if ( m_recState.bRecording && m_ripFile != NULL )
|
|
{
|
|
fwrite(buf, size, 1, m_ripFile);
|
|
}
|
|
}
|
|
|
|
void CShoutcastRipFile::SetTrackname( const char *trackName )
|
|
{
|
|
if ( m_recState.bRecording && m_recState.bHasMetaData) //if we are already recording, swap the file
|
|
{
|
|
StopRecording();
|
|
memset( m_szFileName, '\0', sizeof(m_szFileName)); //clear buffer
|
|
strcpy( m_szFileName, trackName ); //swap
|
|
m_iTrackCount++;
|
|
Record();
|
|
}
|
|
else
|
|
{
|
|
memset( m_szFileName, '\0', sizeof(m_szFileName)); //clear buffer
|
|
strcpy( m_szFileName, trackName ); //swap only filenames
|
|
if ( strcmp( m_szFileName, m_szStreamName ) != 0 ) //sometimes it takes a moment for RM to get the real TrackName
|
|
m_recState.bHasMetaData = true;
|
|
}
|
|
}
|
|
|
|
void CShoutcastRipFile::PrepareRecording( )
|
|
{
|
|
//"init"
|
|
m_Tag.Clear();
|
|
memset( m_szFilteredFileName, '\0', sizeof(m_szFilteredFileName)); //clear buffer
|
|
|
|
|
|
//Get the directory
|
|
//first copy the fileName to preserve info of the original RM message
|
|
strcpy(m_szFilteredFileName, m_szFileName );
|
|
char directoryName[1124];
|
|
GetDirectoryName(directoryName);
|
|
RemoveIllegalChars( directoryName );
|
|
|
|
|
|
|
|
//sometimes it happens, that .mp3 is already at the end, then strip it
|
|
int lastFour = strlen( m_szFilteredFileName ) - 4;
|
|
if ( strcmp( &m_szFilteredFileName[lastFour], ".mp3") == 0 )
|
|
{
|
|
m_szFilteredFileName[lastFour] = '\0';
|
|
}
|
|
RemoveLastSpace( m_szFilteredFileName );
|
|
RemoveIllegalChars( m_szFilteredFileName ); //first remove all unsupported chars
|
|
//now distinguish between
|
|
if ( m_recState.bHasMetaData )
|
|
{
|
|
//The filename of RM will be something like "Oasis - Champagne Supernova", thus
|
|
//So, we will make a file i.e "f:music\Record\Limbik Frequencies\Oasis - Champagne Supernova.mp3"
|
|
|
|
CStdString strHomePath = g_guiSettings.GetString("audiocds.recordingpath");
|
|
char szFilePath[1024];
|
|
sprintf( szFilePath, "%s\\%s", strHomePath.c_str(), directoryName );
|
|
SetFilename( szFilePath, m_szFilteredFileName );
|
|
//get the artist and trackname
|
|
char szArtist[1124];
|
|
char szTrackName[1124];
|
|
char szTokens[1024];
|
|
char* cursor;
|
|
bool tokenUsed = false;
|
|
bool foundArtist = false;
|
|
bool foundTrackName = false;
|
|
strcpy( szTokens, m_szFileName );
|
|
cursor = strtok(szTokens, "-");
|
|
while (cursor != NULL)
|
|
{
|
|
tokenUsed = false;
|
|
//we will look for two strings,
|
|
//sometimes there's a track number, wich we will throw away
|
|
if ( !foundArtist )
|
|
{
|
|
if ( atoi( cursor ) == 0 )
|
|
{
|
|
//its a string
|
|
strcpy(szArtist, cursor );
|
|
foundArtist = true;
|
|
tokenUsed = true;
|
|
}
|
|
}
|
|
if ( !foundTrackName && !tokenUsed )
|
|
{
|
|
if ( atoi( cursor ) == 0 )
|
|
{
|
|
//its a string
|
|
strcpy(szTrackName, cursor );
|
|
foundTrackName = true;
|
|
tokenUsed = true;
|
|
}
|
|
}
|
|
cursor = strtok (NULL, "-");
|
|
}
|
|
if ( foundTrackName ) //lets hope we found a track name, else it will be unknown
|
|
m_Tag.SetTitle(szTrackName);
|
|
if ( foundArtist ) //lets hope we found an artist, else it will be unknown
|
|
m_Tag.SetArtist(szArtist);
|
|
m_Tag.SetAlbum(directoryName); //Jazzmusique (Album is like Directory)
|
|
}
|
|
else
|
|
{
|
|
//here we will make a file i.e "f:music\Record\Jazzmusique\Jazzmusique - 3.mp3"
|
|
CStdString strHomePath = g_guiSettings.GetString("audiocds.recordingpath");
|
|
char szFilePath[1024];
|
|
char szTitle[1124]; //i.e.
|
|
|
|
//Set the filename
|
|
sprintf( szFilePath, "%s\\%s", strHomePath.c_str(), directoryName );
|
|
SetFilename( szFilePath, directoryName ); //file name like Directory
|
|
|
|
//set the remaining tags
|
|
char szTemp[1124];
|
|
strcpy(szTemp, directoryName );
|
|
strcat(szTemp, " %i");
|
|
sprintf(szTitle, szTemp, m_iTrackCount);
|
|
m_Tag.SetTitle(szTitle ); //Jazzmusique 3
|
|
m_Tag.SetAlbum(directoryName ); //Jazzmusique (Album is like Directory)
|
|
m_Tag.SetArtist("Shoutcast"); //Shoutcast
|
|
}
|
|
}
|
|
|
|
void CShoutcastRipFile::SetFilename( const char* filePath, const char* fileName )
|
|
{
|
|
INT i;
|
|
WIN32_FIND_DATA wfd;
|
|
HANDLE hFind;
|
|
|
|
char szNewFileName[1024];
|
|
char szTempFilePath[1024];
|
|
|
|
const int MY_MAX_PATH = 42;
|
|
|
|
//cut the received fileName to max.
|
|
char szMaxFileName[MY_MAX_PATH];
|
|
memset( szMaxFileName, '\0', sizeof(szMaxFileName)); //clear buffer
|
|
if ( strlen( fileName ) > MY_MAX_PATH - 11 ) //-11: Don't forget .mp3 and 100 - !!!
|
|
{
|
|
memmove(szMaxFileName, fileName, MY_MAX_PATH - 11 ); //then copy the appropriate size
|
|
}
|
|
else
|
|
{
|
|
strcpy(szMaxFileName, fileName ); //else, just copy the string
|
|
}
|
|
|
|
strcpy( szTempFilePath, filePath );
|
|
|
|
//first look if we need to create the directory
|
|
memset(&wfd, 0, sizeof(wfd));
|
|
hFind = FindFirstFile(szTempFilePath, &wfd);
|
|
//lets create a directory if it doesn't exist
|
|
if ( wfd.cFileName[0] == 0 )
|
|
{
|
|
CreateDirectory( szTempFilePath, NULL );
|
|
}
|
|
|
|
for ( i = m_iTrackCount; i <= MAX_RECORDED_TRACKS; i++ )
|
|
{
|
|
if ( m_recState.bHasMetaData )
|
|
{
|
|
strcat( szTempFilePath, "\\%i - %s.mp3" ); //will be "TRACKNUMBER - FILENAME.mp3"
|
|
sprintf(szNewFileName, szTempFilePath, i, szMaxFileName );
|
|
}
|
|
else
|
|
{
|
|
strcat( szTempFilePath, "\\%s - %i.mp3" ); //will be "FILENAME - TRACKNUMBER.mp3"
|
|
sprintf(szNewFileName, szTempFilePath, szMaxFileName, i );
|
|
}
|
|
memset(&wfd, 0, sizeof(wfd));
|
|
hFind = FindFirstFile(szNewFileName, &wfd);
|
|
if ( wfd.cFileName[0] == 0 )
|
|
{
|
|
strcpy( m_szFilteredFileName, szNewFileName );
|
|
FindClose( hFind );
|
|
//set the appropriate trackNumber
|
|
m_Tag.SetTrackNumber(i);
|
|
if ( !m_recState.bHasMetaData )
|
|
m_iTrackCount = i;
|
|
|
|
return ;
|
|
}
|
|
else
|
|
{
|
|
strcpy( szTempFilePath, filePath ); //file exists, reset
|
|
}
|
|
}
|
|
//if its the MAX_RECORDED_TRACKS. file, we overwrite it
|
|
strcpy( m_szFilteredFileName, szNewFileName );
|
|
FindClose( hFind );
|
|
return ;
|
|
}
|
|
|
|
void CShoutcastRipFile::RemoveIllegalChars( char *szRemoveIllegal )
|
|
{
|
|
static char legalChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!#$%&'()-@[]^_`{}~ ";
|
|
char *cursor;
|
|
for (cursor = szRemoveIllegal; *(cursor += strspn(cursor, legalChars)); /**/ )
|
|
*cursor = '_';
|
|
}
|
|
|
|
|
|
//Sets the directory to a stripped stream name.
|
|
//Here is where most problems occure: It is hard to implement something fast
|
|
//which works for all streams and results in a meaningful name...
|
|
void CShoutcastRipFile::GetDirectoryName( char* directoryName )
|
|
{
|
|
char szStrippedStreamName[30]; //max 30 chars
|
|
memmove(szStrippedStreamName, m_szStreamName, 30);
|
|
// First start only at the place, where the first alphabetical character occures
|
|
//this is for example: "...::::Beatblender::::...."
|
|
char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
int pos = strcspn (szStrippedStreamName, alphabet);;
|
|
memmove(directoryName, szStrippedStreamName + pos, 29 - pos );
|
|
directoryName[29] = '\0'; //the last one should be \0
|
|
|
|
//sometimes streams look like "Streamname - Shitty info",
|
|
//then remove that Shitty info
|
|
CutAfterLastChar(directoryName, '-' );
|
|
|
|
//or then theylook like "Streamname: Shitty info - other shitty info", :-(
|
|
//then remove that Shitty info
|
|
CutAfterLastChar(directoryName, ':' );
|
|
|
|
RemoveLastSpace(directoryName); //also remove the last space, else if used as path -> crash
|
|
}
|
|
|
|
void CShoutcastRipFile::CutAfterLastChar( char* szToBeCutted, int where )
|
|
{
|
|
char * cursor;
|
|
cursor = strchr( szToBeCutted, where );
|
|
if ( cursor != NULL )
|
|
szToBeCutted[cursor - szToBeCutted] = '\0';
|
|
}
|
|
|
|
void CShoutcastRipFile::RemoveLastSpace( char* szToBeRemoved )
|
|
{
|
|
if ( szToBeRemoved[strlen(szToBeRemoved) - 1] == ' ' )
|
|
szToBeRemoved[strlen(szToBeRemoved) - 1] = '\0';
|
|
}
|
|
|
|
void CShoutcastRipFile::GetMusicInfoTag(CMusicInfoTag& tag)
|
|
{
|
|
if ( m_recState.bFilenameSet && m_recState.bStreamSet )
|
|
{
|
|
tag.Clear();
|
|
//now distinguish between
|
|
if ( m_recState.bHasMetaData )
|
|
{
|
|
//The filename of RM will be something like "Oasis - Champagne Supernova", thus
|
|
//So, we will make a file i.e "f:music\Record\Limbik Frequencies\Oasis - Champagne Supernova.mp3"
|
|
//get the artist and trackname
|
|
char szArtist[1124];
|
|
char szTrackName[1124];
|
|
char szTokens[1024];
|
|
char* cursor;
|
|
bool tokenUsed = false;
|
|
bool foundArtist = false;
|
|
bool foundTrackName = false;
|
|
strcpy( szTokens, m_szFileName );
|
|
cursor = strtok(szTokens, "-");
|
|
if (!cursor) cursor = strtok(szTokens, ",");
|
|
while (cursor != NULL)
|
|
{
|
|
tokenUsed = false;
|
|
//we will look for two strings,
|
|
//sometimes there's a track number, wich we will throw away
|
|
if ( !foundArtist )
|
|
{
|
|
if ( atoi( cursor ) == 0 )
|
|
{
|
|
//its a string
|
|
strcpy(szArtist, cursor );
|
|
foundArtist = true;
|
|
tokenUsed = true;
|
|
}
|
|
}
|
|
if ( !foundTrackName && !tokenUsed )
|
|
{
|
|
if ( atoi( cursor ) == 0 )
|
|
{
|
|
//its a string
|
|
strcpy(szTrackName, cursor );
|
|
foundTrackName = true;
|
|
tokenUsed = true;
|
|
}
|
|
}
|
|
cursor = strtok (NULL, "-");
|
|
//if (!cursor) cursor=strtok(NULL,",");
|
|
}
|
|
if ( foundTrackName ) //lets hope we found a track name, else it will be unknown
|
|
tag.SetTitle(szTrackName);
|
|
else
|
|
tag.SetTitle(m_szStreamName);
|
|
if ( foundArtist ) //lets hope we found an artist, else it will be unknown
|
|
tag.SetArtist(szArtist);
|
|
tag.SetAlbum(m_szStreamName); //Jazzmusique (Album is like Directory)
|
|
}
|
|
else
|
|
{
|
|
tag.Clear();
|
|
tag.SetTitle(m_szStreamName);
|
|
}
|
|
|
|
}
|
|
}
|