winamp/Src/playlist/M3ULoader.cpp
2024-09-24 14:54:57 +02:00

466 lines
12 KiB
C++

#include <stdio.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <fstream>
#include <string>
#include "M3ULoader.h"
#include "../nu/ns_wc.h"
#include "../WAT/WAT.h"
M3ULoader::M3ULoader() : _utf8( false )
{
wideTitle[ 0 ] = wideFilename[ 0 ] = 0;
}
M3ULoader::~M3ULoader( void )
{
//Close();
}
struct cmpWchar_t {
bool operator()(const wchar_t* a, const wchar_t* b) const {
return wcscmp(a, b) < 0;
}
};
class M3UInfo : public ifc_plentryinfo
{
public:
M3UInfo() {}
M3UInfo( wchar_t *_mediahash, wchar_t *_metahash, wchar_t *_cloud_id, wchar_t *_cloud_status, wchar_t *_cloud_devices )
{
_extended_infos.emplace( _wcsdup( _INFO_NAME_MEDIA_HASH ), _wcsdup( _mediahash) );
_extended_infos.emplace( _wcsdup( _INFO_NAME_META_HASH ), _wcsdup( _metahash ) );
_extended_infos.emplace( _wcsdup( _INFO_NAME_CLOUD_ID ), _wcsdup( _cloud_id ) );
_extended_infos.emplace( _wcsdup( _INFO_NAME_CLOUD_STATUS ), _wcsdup( _cloud_status ) );
_extended_infos.emplace( _wcsdup( _INFO_NAME_CLOUD_DEVICES ), _wcsdup( _cloud_devices ) );
}
~M3UInfo()
{
for ( auto l_extended_infos_iterator = _extended_infos.begin(); l_extended_infos_iterator != _extended_infos.end(); ++l_extended_infos_iterator )
{
free( ( *l_extended_infos_iterator ).first );
free( ( *l_extended_infos_iterator ).second );
}
_extended_infos.clear();
}
void SetExtendedInfo( const wchar_t *p_parameter_name, const wchar_t *p_parameter_value )
{
_extended_infos.emplace( _wcsdup( p_parameter_name ), _wcsdup( p_parameter_value ) );
}
const wchar_t *GetExtendedInfo( wchar_t *parameter )
{
//for ( auto l_extended_infos_iterator = _extended_infos.begin(); l_extended_infos_iterator != _extended_infos.end(); ++l_extended_infos_iterator )
//{
// wchar_t *l_key = _wcsdup( ( *l_extended_infos_iterator ).first );
// if ( wcscmp( l_key, parameter ) == 0 )
// return _wcsdup( ( *l_extended_infos_iterator ).second );
//}
// OLD
//std::map<wchar_t *, wchar_t *>::iterator l_extended_infos_iterator = _extended_infos.find( parameter );
//if ( l_extended_infos_iterator != _extended_infos.end() )
// return _wcsdup( ( *l_extended_infos_iterator ).second );
auto it = _extended_infos.find(parameter);
if (_extended_infos.end() != it)
{
return it->second;
}
return 0;
}
private:
RECVS_DISPATCH;
std::map<wchar_t *, wchar_t *, cmpWchar_t> _extended_infos;
};
#define CBCLASS M3UInfo
START_DISPATCH;
CB( IFC_PLENTRYINFO_GETEXTENDEDINFO, GetExtendedInfo )
END_DISPATCH;
#undef CBCLASS
int M3ULoader::OnFileHelper( ifc_playlistloadercallback *playlist, const wchar_t *trackName, const wchar_t *title, int length, const wchar_t *rootPath, ifc_plentryinfo *extraInfo )
{
if ( length == -1000 )
length = -1;
wcsncpy( wideFilename, trackName, FILENAME_SIZE );
int ret;
if ( wcsstr( wideFilename, L"://" ) || PathIsRootW( wideFilename ) )
{
ret = playlist->OnFile( wideFilename, title, length, extraInfo );
}
else
{
wchar_t fullPath[ MAX_PATH ] = { 0 };
if ( PathCombineW( fullPath, rootPath, wideFilename ) )
{
wchar_t canonicalizedPath[ MAX_PATH ] = { 0 };
PathCanonicalizeW( canonicalizedPath, fullPath );
ret = playlist->OnFile( canonicalizedPath, title, length, extraInfo );
}
else
{
ret = ifc_playlistloadercallback::LOAD_CONTINUE;
}
}
return ret;
}
static bool StringEnds( const wchar_t *a, const wchar_t *b )
{
size_t aLen = wcslen( a );
size_t bLen = wcslen( b );
if ( aLen < bLen )
return false; // too short
if ( !_wcsicmp( a + aLen - bLen, b ) )
return true;
return false;
}
int M3ULoader::Load( const wchar_t *p_filename, ifc_playlistloadercallback *playlist )
{
// TODO: download temp file if it's a URL
// TODO - WDP2-198
FILE *fp = _wfopen( p_filename, L"rt,ccs=UNICODE" );
if ( !fp )
return IFC_PLAYLISTLOADER_FAILED;
fseek( fp, 0, SEEK_END );
int size = ftell( fp );
fseek( fp, 0, SEEK_SET );
if ( size == -1 )
{
fclose( fp );
fp = 0;
return IFC_PLAYLISTLOADER_FAILED;
}
if ( StringEnds( p_filename, L".m3u8" ) )
_utf8 = true;
int ext = 0;
wchar_t *p;
const int l_linebuf_size = 2048;
wchar_t linebuf[ l_linebuf_size ] = { 0 };
wchar_t ext_title[ MAX_PATH ] = { 0 };
wchar_t ext_mediahash[ 128 ] = { 0 };
wchar_t ext_metahash[ 128 ] = { 0 };
wchar_t ext_cloud_id[ 128 ] = { 0 };
wchar_t ext_cloud_status[ 16 ] = { 0 };
wchar_t ext_cloud_devices[ 128 ] = { 0 };
int ext_len = -1;
wchar_t rootPath[ MAX_PATH ] = { 0 };
const wchar_t *callbackPath = playlist->GetBasePath();
if ( callbackPath )
StringCchCopyW( rootPath, MAX_PATH, callbackPath );
else
{
StringCchCopyW( rootPath, MAX_PATH, p_filename );
PathRemoveFileSpecW( rootPath );
}
unsigned char BOM[ 3 ] = { 0, 0, 0 };
if ( fread( BOM, 3, 1, fp ) == 1 && BOM[ 0 ] == 0xEF && BOM[ 1 ] == 0xBB && BOM[ 2 ] == 0xBF )
_utf8 = true;
else
fseek( fp, 0, SEEK_SET );
std::wstring l_separator = L"\" ";
std::wstring l_key_separator = L"=";
const wchar_t _ASF[] = L"ASF ";
const wchar_t _DIRECTIVE_EXTINF[] = L"#EXTINF:";
const wchar_t _DIRECTIVE_EXTM3U[] = L"#EXTM3U";
const wchar_t _DIRECTIVE_EXT_X_NS_CLOUD[] = L"#EXT-X-NS-CLOUD:";
const wchar_t _DIRECTIVE_UTF8[] = L"#UTF8";
const wchar_t _END_LINE[] = L"\r\n";
const int l_move_size = sizeof( wchar_t );
wa::strings::wa_string l_key_value_pair = "";
wa::strings::wa_string l_key = "";
wa::strings::wa_string l_value = "";
std::map<std::wstring, std::wstring> l_extended_infos;
while ( 1 )
{
if ( feof( fp ) )
break;
linebuf[ 0 ] = 0;
fgetws( linebuf, l_linebuf_size - 1, fp );
linebuf[ wcscspn( linebuf, _END_LINE ) ] = 0;
if ( wcslen( linebuf ) == 0 )
continue;
if ( ext == 0 && wcsstr( linebuf, _DIRECTIVE_EXTM3U ) )
{
ext = 1;
continue;
}
if ( !wcsncmp( linebuf, _DIRECTIVE_UTF8, 5 ) )
{
_utf8 = true;
continue;
}
p = linebuf;
while ( p && *p == ' ' || *p == '\t' )
p = CharNextW( p );
if ( *p != '#' && *p != '\n' && *p != '\r' && *p )
{
wchar_t buf[ 4096 ] = { 0 };
wchar_t *p2 = CharPrevW( linebuf, linebuf + wcslen( linebuf ) ); //GetLastCharacter(linebuf);
if ( p2 && *p2 == '\n' )
*p2 = 0;
if ( !wcsncmp( p, _ASF, 4 ) && wcslen( p ) > 4 )
p += 4;
if ( wcsncmp( p, L"\\\\", 2 ) && wcsncmp( p + 1, L":\\", 2 ) && wcsncmp( p + 1, L":/", 2 ) && !wcsstr( p, L"://" ) )
{
if ( p[ 0 ] == '\\' )
{
buf[ 0 ] = rootPath[ 0 ];
buf[ 1 ] = rootPath[ 1 ];
StringCchCopyW( buf + 2, 4093, p );
//buf[ wcslen( buf ) - 1 ] = 0;
buf[ wcscspn( buf, _END_LINE ) ] = 0;
p = buf;
}
}
int ret;
// generate extra info from the cloud specific values (if present)
M3UInfo info( ext_mediahash, ext_metahash, ext_cloud_id, ext_cloud_status, ext_cloud_devices );
if ( !l_extended_infos.empty() )
{
for ( auto l_extended_infos_iterator = l_extended_infos.begin(); l_extended_infos_iterator != l_extended_infos.end(); ++l_extended_infos_iterator )
{
info.SetExtendedInfo( ( *l_extended_infos_iterator ).first.c_str(), ( *l_extended_infos_iterator ).second.c_str() );
}
}
l_extended_infos.clear();
if ( ext_title[ 0 ] )
{
wcsncpy( wideTitle, ext_title, FILETITLE_SIZE );
ret = OnFileHelper( playlist, p, wideTitle, ext_len * 1000, rootPath, &info );
}
else
{
ret = OnFileHelper( playlist, p, 0, -1, rootPath, &info );
}
if ( ret != ifc_playlistloadercallback::LOAD_CONTINUE )
break;
ext_len = -1;
ext_title[ 0 ] = 0;
}
else
{
if ( ext && !wcsncmp( p, _DIRECTIVE_EXTINF, 8 ) )
{
p += 8;
ext_len = _wtoi( p );
int l_track_length = ext_len;
int l_digits = ( l_track_length < 0 ? 1 : 0 );
while ( l_track_length )
{
l_track_length /= 10;
++l_digits;
}
p += l_digits;
if ( p && *p )
{
wchar_t *p2 = CharPrevW( p, p + wcslen( p ) ); // GetLastCharacter(p);
if ( p2 && *p2 == '\n' )
*p2 = 0;
while ( p && *p == ' ' )
p = CharNextW( p );
std::wstring l_string( p );
int l_pos = l_string.find_first_of( L"," );
if ( l_pos > 0 )
{
int l_key_separator_pos = 0;
wa::strings::wa_string l_line_trail( l_string.substr( 0, l_pos ) );
while ( !l_line_trail.empty() )
{
int l_separator_pos = l_line_trail.find( l_separator );
if ( l_separator_pos > 0 )
l_key_value_pair = l_line_trail.mid( 0, l_separator_pos + 1 );
else
l_key_value_pair = l_line_trail;
l_key_separator_pos = l_key_value_pair.find( l_key_separator );
l_key = l_key_value_pair.mid( 0, l_key_separator_pos );
l_value = l_key_value_pair.mid( l_key_separator_pos + 1, l_key_value_pair.lengthS() - l_key_separator_pos + 1 );
l_value.replaceAll( "\"", "" );
l_extended_infos.emplace( l_key.GetW(), l_value.GetW() );
if ( l_separator_pos > 0 )
l_line_trail = l_line_trail.mid( l_separator_pos + l_move_size, l_line_trail.lengthS() - l_separator_pos + 1 );
else
l_line_trail.clear();
}
l_string = l_string.substr( l_pos + 1, l_string.size() - l_pos );
StringCchCopyW( ext_title, MAX_PATH, l_string.c_str() );
}
else
StringCchCopyW( ext_title, MAX_PATH, CharNextW( p ) );
}
else
{
ext_len = -1;
ext_title[ 0 ] = 0;
}
}
// cloud specific playlist line for holding information about the entry
else if ( ext && !wcsncmp( p, _DIRECTIVE_EXT_X_NS_CLOUD, 16 ) )
{
p += 16;
wchar_t *pt = wcstok( p, L"," );
while ( pt != NULL )
{
int end = (int)wcscspn( pt, L"=" );
if ( !wcsncmp( pt, _INFO_NAME_MEDIA_HASH, end ) )
{
if ( ( lstrcpynW( ext_mediahash, pt + end + 1, 128 ) ) == NULL )
return IFC_PLAYLISTLOADER_FAILED;
}
else if ( !wcsncmp( pt, _INFO_NAME_META_HASH, end ) )
{
if ( ( lstrcpynW( ext_metahash, pt + end + 1, 128 ) ) == NULL )
return IFC_PLAYLISTLOADER_FAILED;
}
else if ( !wcsncmp( pt, _INFO_NAME_CLOUD_ID, end ) )
{
if ( ( lstrcpynW( ext_cloud_id, pt + end + 1, 128 ) ) == NULL )
return IFC_PLAYLISTLOADER_FAILED;
}
else if ( !wcsncmp( pt, _INFO_NAME_CLOUD_STATUS, end ) )
{
if ( ( lstrcpynW( ext_cloud_status, pt + end + 1, 16 ) ) == NULL )
return IFC_PLAYLISTLOADER_FAILED;
}
else if ( !wcsncmp( pt, _INFO_NAME_CLOUD_DEVICES, end ) )
{
wchar_t *p2 = pt + end + 1;
while ( p2 && *p2 != '\n' )
p2 = CharNextW( p2 );
if ( p2 && *p2 == '\n' )
*p2 = 0;
if ( ( lstrcpynW( ext_cloud_devices, pt + end + 1, 128 ) ) == NULL )
return IFC_PLAYLISTLOADER_FAILED;
}
pt = wcstok( NULL, L"," );
}
}
else
{
ext_len = -1;
ext_title[ 0 ] = 0;
ext_mediahash[ 0 ] = 0;
ext_metahash[ 0 ] = 0;
ext_cloud_id[ 0 ] = 0;
ext_cloud_status[ 0 ] = 0;
ext_cloud_devices[ 0 ] = 0;
}
}
}
if ( fp )
fclose( fp );
return IFC_PLAYLISTLOADER_SUCCESS;
}
#ifdef CBCLASS
#undef CBCLASS
#endif
#define CBCLASS M3ULoader
START_DISPATCH;
CB( IFC_PLAYLISTLOADER_LOAD, Load )
#if 0
VCB( IFC_PLAYLISTLOADER_CLOSE, Close )
CB( IFC_PLAYLISTLOADER_GETITEM, GetItem )
CB( IFC_PLAYLISTLOADER_GETITEMTITLE, GetItemTitle )
CB( IFC_PLAYLISTLOADER_GETITEMLENGTHMILLISECONDS, GetItemLengthMilliseconds )
CB( IFC_PLAYLISTLOADER_GETITEMEXTENDEDINFO, GetItemExtendedInfo )
CB( IFC_PLAYLISTLOADER_NEXTITEM, NextItem )
#endif
END_DISPATCH;