Files
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

516 lines
15 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// FILE : VideoFileDataProvider.cpp
// PURPOSE : Provides access to details relevant to video files
//
// AUTHOR : james.strain
//
// Copyright (C) 1999-2013 Rockstar Games. All Rights Reserved.
//
/////////////////////////////////////////////////////////////////////////////////
#include "VideoFileDataProvider.h"
#if defined( GTA_REPLAY ) && GTA_REPLAY
#if defined(VIDEO_PLAYBACK_ENABLED) && VIDEO_PLAYBACK_ENABLED
// rage
#include "atl/string.h"
#include "file/cachepartition.h"
#include "file/asset.h"
// framework
#include "fwlocalisation/textUtil.h"
#include "video/media_transcoding_allocator.h"
#include "video/VideoPlaybackThumbnailManager.h"
#include "video/VideoPlaybackSettings.h"
// game
#include "system/FileMgr.h"
#include "control/replay/file/ReplayFileManager.h"
#if RSG_ORBIS
#include "renderer/rendertargets.h"
#include "video/contentDelete_Orbis.h"
#elif RSG_DURANGO
#include "control/replay/File/FileStoreDurango.h"
#endif
FRONTEND_OPTIMISATIONS();
CVideoFileDataProvider::CVideoFileDataProvider( IImageLoader& imageLoader )
: CThumbnailFileDataProvider( imageLoader )
{
#if RSG_ORBIS
m_videoFileDataEnumerator.StartThread();
#endif
}
CVideoFileDataProvider::~CVideoFileDataProvider()
{
#if RSG_ORBIS
m_videoFileDataEnumerator.StopThread();
#endif
}
bool CVideoFileDataProvider::initialize( char const * const directoryPath, char const * const filter, u64 const activeUserId )
{
char path[RAGE_MAX_PATH];
ReplayFileManager::FixName(path, directoryPath);
return CThumbnailFileDataProvider::initialize( path, filter, activeUserId );
}
bool CVideoFileDataProvider::EnumerationFunctionImpl( const char* filepath, FileDataStorage& fileList, const char* filter )
{
#if RSG_ORBIS
m_videoFileDataEnumerator.StartEnumeration(&m_fileData, &m_videoData, m_activeUserId);
return true;
#elif RSG_PC || RSG_DURANGO
MediaCommon::ePrepareState threadPrepareState( MediaCommon::STATE_NONE );
threadPrepareState = MediaCommon::PrepareThread();
#if RSG_PC
bool const c_success = videoVerifyf( threadPrepareState == MediaCommon::STATE_PREPARED, "CVideoFileDataProvider::EnumerationFunctionImpl - "
"Failed to initialize media foundation, thread prepare state was %d", threadPrepareState ) ?
CFileDataProvider::EnumerationFunctionImpl( filepath, fileList, filter ) : false;
#else
bool const c_success = videoVerifyf( threadPrepareState == MediaCommon::STATE_PREPARED, "CVideoFileDataProvider::EnumerationFunctionImpl - "
"Failed to initialize media foundation, thread prepare state was %d", threadPrepareState ) ?
BuildVideoList( filepath, fileList, filter, true ) : false;
#endif
MediaCommon::CleanupThread( threadPrepareState );
return c_success;
#endif
}
bool CVideoFileDataProvider::DeleteFunctionImpl( const char* filepath )
{
#if RSG_ORBIS
return CContentDelete::DoDeleteVideo(filepath);
#else
return ReplayFileManager::StartDeleteFile( filepath );
#endif
}
bool CVideoFileDataProvider::CheckDeleteFunctionImpl( bool& result ) const
{
#if RSG_ORBIS
result = true;
return true;
#else
return ReplayFileManager::CheckDeleteFile( result );
#endif
}
#if RSG_DURANGO
bool CVideoFileDataProvider::BuildVideoList( const char* filepath, FileDataStorage& fileList, const char* filter, bool initialEntry )
{
bool success = false;
videoDisplayf("BuildVideoList - %s", filepath);
fiDevice const * c_dev = filepath ? fiDevice::GetDevice( filepath ) : NULL;
if( c_dev )
{
// As long as we got the device, we have succeeded since we may not find any files to match our filter.
success = true;
u64 userId = 0;
if( !initialEntry )
{
char pathBufferForUserId[RAGE_MAX_PATH];
formatf(pathBufferForUserId, strlen(filepath), "%s", filepath);
char* lastSlash = strrchr(pathBufferForUserId, '\\');
if (lastSlash == NULL)
{
lastSlash = strrchr(pathBufferForUserId, '/');
}
if (lastSlash[1] >= '0' && lastSlash[1] <= '9')
{
userId = _strtoui64(&lastSlash[1], NULL, 10);
}
}
// Enumerate directory for files
fiFindData findData;
CFileData scratchData;
fiHandle hFileHandle = fiHandleInvalid;
bool validDataFound = true;
char name[RAGE_MAX_PATH];
DisplayName displayName;
PathBuffer pathBuffer;
FileDataIndex existingIndex;
// todo - maximum amount of files allowed?
for( hFileHandle = FindFileBegin( filepath, findData, c_dev );
fiIsValidHandle( hFileHandle ) && validDataFound;
validDataFound = FindFileNext( hFileHandle, findData, c_dev ) )
{
s32 nameLength = static_cast<s32>(strlen(findData.m_Name));
if( ( findData.m_Attributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
{
#if SEE_ALL_USERS_FILES
// make sure directory is a proper user directory and not just '.' or '..'
if (nameLength > REPLAYDIRMINLENGTH)
{
replayDisplayf("ENUMERATE CLIPS - directory - %s", findData.m_Name);
// should enumerate this directory if requested
char childPath[REPLAYPATHLENGTH] = { 0 };
formatf_n( childPath, REPLAYPATHLENGTH, "%s%s/", filepath, findData.m_Name );
BuildVideoList( childPath, fileList, filter );
}
#endif
continue;
}
// If the filename+path is greater than REPLAYPATHLENGTH, then discard (since we can't cache it & thus can't load the file later using the name)
if( nameLength > (REPLAYPATHLENGTH - 1) ) // -1 for the zero terminator that will be required.
continue;
#if SEE_ALL_USERS_FILES
// if base directory, don't list any files ... shouldn't be any in here, but might catch old dev stuff
if (initialEntry)
continue;
#endif
bool fileIsInvalid = false;
bool attemptToAddFile = true;
safecpy( name, findData.m_Name );
if( ( filter == NULL || strstr( strlwr( name ), filter ) != NULL ) &&
strcmp( findData.m_Name, "." ) != 0 && strcmp( findData.m_Name, ".." ) != 0 )
{
if( strlen( findData.m_Name ) + 1 < scratchData.getMaxFilenameSizeBytes() )
{
// Check if this file already exists, if it does check if it changed.
existingIndex = getIndexForFileName( findData.m_Name, false, userId );
if( existingIndex != FileIndexInvald )
{
CFileData& oldData = fileList[existingIndex];
if( oldData.getLastWriteTime() != findData.m_LastWriteTime )
{
fileList.erase( fileList.begin() + existingIndex );
}
else
{
attemptToAddFile = false;
}
}
if( attemptToAddFile )
{
if( ValidateFileAndPopulateCustomData( filepath, findData.m_Name, scratchData ) )
{
scratchData.setFilename( findData.m_Name );
displayName = findData.m_Name;
fwTextUtil::TruncateFileExtension( displayName.getWritableBuffer() );
scratchData.setDisplayName( displayName.getBuffer() );
scratchData.setLastWriteTime( findData.m_LastWriteTime );
scratchData.setSize( findData.m_Size );
scratchData.setUserId( userId );
fileList.PushAndGrow( scratchData );
}
else
{
uiWarningf( "CVideoFileDataProvider::BuildVideoList - File %s matches filters but is not a valid file!", findData.m_Name );
fileIsInvalid = true;
}
}
}
else
{
uiWarningf( "CVideoFileDataProvider::BuildVideoList - File %s has too long a name! Max name size is be %" SIZETFMT "u" ,
findData.m_Name, scratchData.getMaxFilenameSizeBytes() );
fileIsInvalid = true;
}
if( fileIsInvalid && DeleteInvalidFilesOnEnum() )
{
pathBuffer.sprintf( "%s%s", filepath, findData.m_Name );
uiWarningf( "CVideoFileDataProvider::BuildVideoList - Deleting %s as it is invalid", pathBuffer.getBuffer() );
DeleteFunctionImpl( pathBuffer.getBuffer() );
}
}
}
if( fiIsValidHandle( hFileHandle ) )
{
c_dev->FindFileEnd( hFileHandle );
}
}
return success;
}
#endif
void CVideoFileDataProvider::releaseAllThumbnails()
{
VideoPlaybackThumbnailManager::releaseAllImages();
}
bool CVideoFileDataProvider::getFilePath( FileDataIndex const index, char (&out_buffer)[ RAGE_MAX_PATH ] ) const
{
bool const result = CThumbnailFileDataProvider::getFilePath( index, out_buffer );
return result;
}
bool CVideoFileDataProvider::getFilePath( CFileData const& fileData, char (&out_buffer)[ RAGE_MAX_PATH ] ) const
{
#if RSG_ORBIS
// on PS4, we build a full path from the av_content info, rather than a set folder, as the paths can be different
out_buffer[0] = rage::TEXT_CHAR_TERMINATOR;
CFileDataVideo const * videoData = getVideoData( fileData );
if( videoData )
{
formatf_n( out_buffer, RAGE_MAX_PATH, "%s/%s", videoData->getPath(), fileData.getFilename() );
}
return out_buffer[0] != rage::TEXT_CHAR_TERMINATOR;
#else
bool const result = CThumbnailFileDataProvider::getFilePath( fileData, out_buffer );
return result;
#endif
}
bool CVideoFileDataProvider::getFileDirectory( CFileData const& fileData, char (&out_buffer)[ RAGE_MAX_PATH ] ) const
{
#if RSG_ORBIS
// on PS4, we build a full path from the av_content info, rather than a set folder, as the paths can be different
out_buffer[0] = rage::TEXT_CHAR_TERMINATOR;
CFileDataVideo const * videoData = getVideoData( fileData );
if( videoData )
{
formatf_n( out_buffer, RAGE_MAX_PATH, "%s/", videoData->getPath() );
}
return out_buffer[0] != rage::TEXT_CHAR_TERMINATOR;
#else
bool const result = CThumbnailFileDataProvider::getFileDirectory( fileData, out_buffer );
return result;
#endif
}
u32 CVideoFileDataProvider::GetVideoDurationMs( FileDataIndex const index ) const
{
CFileData const * fileData = getFileData( index );
u32 const c_durationMs = fileData ? fileData->getExtraDataU32() : 0;
return c_durationMs;
}
#if RSG_ORBIS
bool CVideoFileDataProvider::CheckEnumerationFunctionImpl( bool& result ) const
{
return m_videoFileDataEnumerator.CheckEnumeration(result);
}
s64 CVideoFileDataProvider::GetVideoContentId( FileDataIndex const index ) const
{
if( uiVerifyf( index < getFileCount(), "CVideoFileDataProvider::GetVideoContentId - Invalid file data index" ) )
{
PathBuffer path;
CFileDataVideo const * videoData = getVideoData( index );
if (videoData)
{
return videoData->getContentId();
}
}
return 0;
}
#endif // RSG_ORBIS
bool CVideoFileDataProvider::ValidateFileAndPopulateCustomData( char const * const directoryPath, char const * const fileName, CFileData& out_data )
{
bool valid = CThumbnailFileDataProvider::ValidateFileAndPopulateCustomData( directoryPath, fileName, out_data ) &&
( ( MediaCommon::IsMp4Supported() && fwTextUtil::HasFileExtension( fileName, ".mp4") ) ||
( MediaCommon::IsWmvSupported() && fwTextUtil::HasFileExtension(fileName, ".wmv") ) );
if( valid )
{
PathBuffer path( directoryPath );
path.append( fileName );
u32 durationMs(0);
valid = VideoPlayback::IsVideoSupported( path.getBuffer(), MAX_VIDEO_WIDTH, MAX_VIDEO_HEIGHT, MIN_VIDEO_DURATION_MS, NULL, NULL, &durationMs );
out_data.setExtraDataU32( durationMs );
}
return valid;
}
bool CVideoFileDataProvider::generateThumbnailPath( char const * const in_filePath, char (&out_buffer)[ RAGE_MAX_PATH ] ) const
{
bool success = false;
if( uiVerifyf( in_filePath, "CVideoFileDataProvider::generateThumbnailPath - Null path provided!" ) )
{
safecpy( out_buffer, in_filePath );
success = true;
}
return success;
}
bool CVideoFileDataProvider::generateThumbnailPath( char const * const in_path, char const * const in_fileName,
char (&out_path)[ RAGE_MAX_PATH ], char (&out_fileName)[ RAGE_MAX_PATH ] ) const
{
bool success = false;
if( uiVerifyf( in_path, "CVideoFileDataProvider::generateThumbnailPath - Null path provided!" ) &&
uiVerifyf( in_fileName, "CVideoFileDataProvider::generateThumbnailPath - Null path provided!" ) )
{
safecpy( out_path, in_path );
safecpy( out_fileName, in_fileName );
success = true;
}
return success;
}
#if RSG_ORBIS
bool CVideoFileDataProvider::requestThumbnail( FileDataIndex const fileIndex )
{
bool success = false;
uiAssertf( isInitialized(), "CThumbnailFileDataProvider::requestThumbnail - Not initialized!" );
if( isInitialized() )
{
char pathBuffer[ RAGE_MAX_PATH ];
if( getFilePath( fileIndex, pathBuffer ) )
{
char sourcePathBuffer[ RAGE_MAX_PATH ];
if( generateThumbnailPath( pathBuffer, sourcePathBuffer ) )
{
// Just re-used the path buffer to get the thumbnail name
if( !isThumbnailRequested( fileIndex ) && getThumbnailName( fileIndex, pathBuffer ) )
{
atFinalHashString textureName( pathBuffer );
// For PS4 loading thumbnails from av_content
// we need the previous data that the other platforms need for searches and whatnot
// but here we use the proper thumbnail location on av_content to actually load it
CFileDataVideo const * videoData = getVideoData( fileIndex );
if (videoData && videoData->getThumbnail() && (strlen(videoData->getThumbnail()) > 5) )
{
success = m_imageLoader.requestImage( videoData->getThumbnail(), textureName, VideoResManager::DOWNSCALE_HALF );
}
}
}
}
}
return success;
}
CFileDataProvider::FileDataIndex CVideoFileDataProvider::getIndexForDisplayName( char const * const displayName, u64 const /*userId*/ ) const
{
FileDataIndex returnIndex = FileIndexInvald;
returnIndex = CThumbnailFileDataProvider::getIndexForDisplayName(displayName);
if (returnIndex != FileIndexInvald)
{
// if the file has a size of 0, then that means it's a recent display name that's pre-enumeration
const CFileData* fileData = getFileData(returnIndex);
if (fileData->getSize() == 0)
return returnIndex;
// if the file doesn't exist, it might have been deleted in the system software since the last enumeration
char pathBuffer[RAGE_MAX_PATH];
if (getFilePath(returnIndex, pathBuffer))
{
if ( ASSET.Exists(pathBuffer, "mp4") || ASSET.Exists(pathBuffer, "wmv") )
return returnIndex;
}
}
return FileIndexInvald;
}
void CVideoFileDataProvider::TempAddVideoDisplayName(const char* title)
{
// set a display name, and size of zero
// we use the size to tell us not to look to carefully
// need a temp addition to avoid big enumerations at the end of exports that can cause stalls
CFileData scratchData;
scratchData.setDisplayName( title );
scratchData.setSize(0);
m_fileData.PushAndGrow( scratchData );
}
CFileDataVideo const * CVideoFileDataProvider::getVideoData( FileDataIndex const index ) const
{
uiAssertf( isInitialized(), "CVideoFileDataProvider::getVideoData - Attempting to access file data before initialized!" );
uiAssertf( isValidIndex( index ), "CVideoFileDataProvider::getVideoData - Invalid index %d!", index );
return isInitialized() && isValidIndex( index ) ? &m_videoData[ index ] : NULL;
}
CFileDataVideo const * CVideoFileDataProvider::getVideoData( CFileData const& fileData ) const
{
uiAssertf( isInitialized(), "CVideoFileDataProvider::getVideoData - Attempting to access file data before initialized!" );
FileDataIndex const c_dataIndex = getIndexForFiledata( fileData );
uiAssertf( isValidIndex( c_dataIndex ), "CVideoFileDataProvider::getVideoData - Invalid index %d!", c_dataIndex );
return isInitialized() && isValidIndex( c_dataIndex ) ? &m_videoData[ c_dataIndex ] : NULL;
}
void CVideoFileDataProvider::preCleanup()
{
CThumbnailFileDataProvider::releaseAllThumbnails();
m_videoData.Reset();
}
void CVideoFileDataProvider::RemoveEntry( FileDataIndex const index )
{
if( isInitialized() && isValidIndex( index ) )
{
m_fileData.erase( m_fileData.begin() + index );
m_videoData.erase( m_videoData.begin() + index);
}
}
#endif
#endif // defined(VIDEO_PLAYBACK_ENABLED) && VIDEO_PLAYBACK_ENABLED
#endif // defined( GTA_REPLAY ) && GTA_REPLAY