feat: Add comprehensive documentation suite and reorganize project structure

- Created complete documentation in docs/ directory
- Added PROJECT_OVERVIEW.md with feature highlights and getting started guide
- Added ARCHITECTURE.md with system design and technical details
- Added SECURITY.md with comprehensive security implementation guide
- Added DEVELOPMENT.md with development workflows and best practices
- Added DEPLOYMENT.md with production deployment instructions
- Added API.md with complete REST API documentation
- Added CONTRIBUTING.md with contribution guidelines
- Added CHANGELOG.md with version history and migration notes
- Reorganized all documentation files into docs/ directory for better organization
- Updated README.md with proper documentation links and quick navigation
- Enhanced project structure with professional documentation standards
This commit is contained in:
SamiAhmed7777
2025-10-21 00:39:45 -07:00
commit 0b7e2d0a5b
6080 changed files with 1332936 additions and 0 deletions

View File

@@ -0,0 +1,373 @@
<?php
/*******************************************************************************************************************
| Software Name : EasyStream
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
| Software Author : (c) Sami Ahmed
|*******************************************************************************************************************
|
|*******************************************************************************************************************
| This source file is subject to the EasyStream Proprietary License Agreement.
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|*******************************************************************************************************************
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|*******************************************************************************************************************/
defined('_ISVALID') or header('Location: /error');
/**
* Subtitle/Caption Management Class
*
* Handles:
* - Subtitle file uploads (.srt, .vtt)
* - SRT to VTT conversion
* - Multiple language tracks
* - Default subtitle selection
* - Auto-generated caption integration
* - Subtitle deletion and management
*/
class VSubtitles
{
/**
* Upload subtitle file for a video
*
* @param int $file_id File ID
* @param string $file_type File type (video, short, live, audio)
* @param array $subtitle_file $_FILES array element
* @param string $language Language code (en, es, fr, etc.)
* @param string $label Display label (English, Spanish, etc.)
* @param bool $is_default Set as default track
* @return array ['success' => bool, 'message' => string, 'sub_id' => int]
*/
public static function uploadSubtitle($file_id, $file_type, $subtitle_file, $language = 'en', $label = 'English', $is_default = false)
{
global $cfg, $class_database;
// Get configuration
$config = $class_database->getConfigurations('subtitles_enabled,subtitles_max_size,subtitles_allowed_formats,subtitles_auto_convert,subtitles_max_per_video,subtitles_require_approval');
// Check if subtitles are enabled
if ($config['subtitles_enabled'] != 1) {
return ['success' => false, 'message' => 'Subtitles are not enabled on this platform'];
}
// Validate file upload
if (!isset($subtitle_file['tmp_name']) || $subtitle_file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'No file uploaded or upload error occurred'];
}
// Validate file size
if ($subtitle_file['size'] > $config['subtitles_max_size']) {
$max_mb = round($config['subtitles_max_size'] / 1048576, 2);
return ['success' => false, 'message' => "Subtitle file too large. Maximum size: {$max_mb}MB"];
}
// Validate file format
$file_ext = strtolower(pathinfo($subtitle_file['name'], PATHINFO_EXTENSION));
$allowed_formats = explode(',', $config['subtitles_allowed_formats']);
if (!in_array($file_ext, $allowed_formats)) {
return ['success' => false, 'message' => 'Invalid subtitle format. Allowed: ' . implode(', ', $allowed_formats)];
}
// Check subtitle count limit
$current_count = self::getSubtitleCount($file_id, $file_type);
if ($current_count >= $config['subtitles_max_per_video']) {
return ['success' => false, 'message' => "Maximum {$config['subtitles_max_per_video']} subtitle tracks allowed per video"];
}
// Get user ID
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
if ($usr_id == 0) {
return ['success' => false, 'message' => 'You must be logged in to upload subtitles'];
}
// Verify file ownership
if (!self::verifyFileOwnership($file_id, $file_type, $usr_id)) {
return ['success' => false, 'message' => 'You do not have permission to add subtitles to this file'];
}
// Generate unique filename
$unique_filename = self::generateSubtitleFilename($file_id, $file_type, $language, $file_ext);
$subtitle_dir = $cfg['main_dir'] . '/f_data/data_subtitles/';
$destination = $subtitle_dir . $unique_filename;
// Ensure directory exists
if (!file_exists($subtitle_dir)) {
mkdir($subtitle_dir, 0755, true);
}
// Move uploaded file
if (!move_uploaded_file($subtitle_file['tmp_name'], $destination)) {
return ['success' => false, 'message' => 'Failed to save subtitle file'];
}
// Convert SRT to VTT if needed
$final_format = $file_ext;
if ($file_ext === 'srt' && $config['subtitles_auto_convert'] == 1) {
$vtt_filename = self::convertSrtToVtt($destination, $unique_filename);
if ($vtt_filename) {
unlink($destination); // Remove SRT file
$unique_filename = $vtt_filename;
$final_format = 'vtt';
}
}
// If this is set as default, unset other defaults
if ($is_default) {
self::unsetDefaultSubtitles($file_id, $file_type);
}
// Insert into database
$active = ($config['subtitles_require_approval'] == 1 && !self::isAdmin()) ? 0 : 1;
$sql = "INSERT INTO `db_subtitles`
(`file_id`, `file_type`, `usr_id`, `sub_language`, `sub_label`, `sub_filename`,
`sub_format`, `sub_kind`, `sub_default`, `sub_filesize`, `active`)
VALUES (%d, '%s', %d, '%s', '%s', '%s', '%s', 'subtitles', %d, %d, %d)";
$class_database->doQuery($sql,
$file_id,
$class_database->safe_input($file_type),
$usr_id,
$class_database->safe_input($language),
$class_database->safe_input($label),
$class_database->safe_input($unique_filename),
$final_format,
$is_default ? 1 : 0,
filesize($subtitle_dir . $unique_filename),
$active
);
$sub_id = $class_database->insert_id();
// Update subtitle count in file table
self::updateSubtitleCount($file_id, $file_type);
$message = $active ? 'Subtitle uploaded successfully' : 'Subtitle uploaded and pending approval';
return ['success' => true, 'message' => $message, 'sub_id' => $sub_id];
}
/**
* Get all subtitles for a file
*
* @param int $file_id File ID
* @param string $file_type File type
* @param bool $active_only Only return active subtitles
* @return array Array of subtitle records
*/
public static function getSubtitles($file_id, $file_type, $active_only = true)
{
global $class_database;
$active_clause = $active_only ? "AND `active` = 1" : "";
$sql = "SELECT * FROM `db_subtitles`
WHERE `file_id` = %d AND `file_type` = '%s' $active_clause
ORDER BY `sub_default` DESC, `sub_language` ASC";
$result = $class_database->doQuery($sql, $file_id, $class_database->safe_input($file_type));
$subtitles = [];
while ($row = $result->fetch_assoc()) {
$subtitles[] = $row;
}
return $subtitles;
}
/**
* Get subtitle track URL for player
*
* @param int $file_id File ID
* @param string $file_type File type
* @return array Array of subtitle tracks for video player
*/
public static function getSubtitleTracksForPlayer($file_id, $file_type)
{
global $cfg;
$subtitles = self::getSubtitles($file_id, $file_type, true);
$tracks = [];
foreach ($subtitles as $sub) {
$tracks[] = [
'kind' => $sub['sub_kind'],
'label' => $sub['sub_label'],
'srclang' => $sub['sub_language'],
'src' => $cfg['main_url'] . '/f_data/data_subtitles/' . $sub['sub_filename'],
'default' => $sub['sub_default'] == 1
];
}
return $tracks;
}
/**
* Delete subtitle track
*
* @param int $sub_id Subtitle ID
* @return bool Success
*/
public static function deleteSubtitle($sub_id)
{
global $cfg, $class_database;
// Get subtitle info
$sql = "SELECT * FROM `db_subtitles` WHERE `sub_id` = %d LIMIT 1";
$result = $class_database->doQuery($sql, $sub_id);
$subtitle = $result->fetch_assoc();
if (!$subtitle) {
return false;
}
// Verify ownership
$usr_id = isset($_SESSION['USER_ID']) ? (int) $_SESSION['USER_ID'] : 0;
if ($subtitle['usr_id'] != $usr_id && !self::isAdmin()) {
return false;
}
// Delete file
$file_path = $cfg['main_dir'] . '/f_data/data_subtitles/' . $subtitle['sub_filename'];
if (file_exists($file_path)) {
unlink($file_path);
}
// Delete from database
$sql = "DELETE FROM `db_subtitles` WHERE `sub_id` = %d";
$class_database->doQuery($sql, $sub_id);
// Update count
self::updateSubtitleCount($subtitle['file_id'], $subtitle['file_type']);
return true;
}
/**
* Convert SRT to VTT format
*
* @param string $srt_path Path to SRT file
* @param string $original_filename Original filename
* @return string|false VTT filename or false on failure
*/
private static function convertSrtToVtt($srt_path, $original_filename)
{
global $cfg;
$srt_content = file_get_contents($srt_path);
if ($srt_content === false) {
return false;
}
// Add WEBVTT header
$vtt_content = "WEBVTT\n\n";
// Convert timestamps from SRT format (00:00:00,000) to VTT format (00:00:00.000)
$vtt_content .= str_replace(',', '.', $srt_content);
// Generate VTT filename
$vtt_filename = str_replace('.srt', '.vtt', $original_filename);
$vtt_path = $cfg['main_dir'] . '/f_data/data_subtitles/' . $vtt_filename;
// Write VTT file
if (file_put_contents($vtt_path, $vtt_content) === false) {
return false;
}
return $vtt_filename;
}
/**
* Generate unique subtitle filename
*/
private static function generateSubtitleFilename($file_id, $file_type, $language, $ext)
{
return $file_type . '_' . $file_id . '_' . $language . '_' . time() . '.' . $ext;
}
/**
* Unset default flag for all other subtitles
*/
private static function unsetDefaultSubtitles($file_id, $file_type)
{
global $class_database;
$sql = "UPDATE `db_subtitles` SET `sub_default` = 0
WHERE `file_id` = %d AND `file_type` = '%s'";
$class_database->doQuery($sql, $file_id, $class_database->safe_input($file_type));
}
/**
* Get subtitle count for a file
*/
private static function getSubtitleCount($file_id, $file_type)
{
global $class_database;
$sql = "SELECT COUNT(*) as count FROM `db_subtitles`
WHERE `file_id` = %d AND `file_type` = '%s' AND `active` = 1";
$result = $class_database->doQuery($sql, $file_id, $class_database->safe_input($file_type));
$row = $result->fetch_assoc();
return (int) $row['count'];
}
/**
* Update subtitle count in file table
*/
private static function updateSubtitleCount($file_id, $file_type)
{
global $class_database;
$count = self::getSubtitleCount($file_id, $file_type);
$table_map = [
'video' => 'db_videofiles',
'short' => 'db_shortfiles',
'live' => 'db_livefiles',
'audio' => 'db_audiofiles'
];
if (isset($table_map[$file_type])) {
$sql = "UPDATE `{$table_map[$file_type]}` SET `subtitle_count` = %d WHERE `db_id` = %d";
$class_database->doQuery($sql, $count, $file_id);
}
}
/**
* Verify file ownership
*/
private static function verifyFileOwnership($file_id, $file_type, $usr_id)
{
global $class_database;
$table_map = [
'video' => 'db_videofiles',
'short' => 'db_shortfiles',
'live' => 'db_livefiles',
'audio' => 'db_audiofiles'
];
if (!isset($table_map[$file_type])) {
return false;
}
$sql = "SELECT `usr_id` FROM `{$table_map[$file_type]}` WHERE `db_id` = %d LIMIT 1";
$result = $class_database->doQuery($sql, $file_id);
$row = $result->fetch_assoc();
return $row && $row['usr_id'] == $usr_id;
}
/**
* Check if current user is admin
*/
private static function isAdmin()
{
return isset($_SESSION['USER_ADMIN']) && $_SESSION['USER_ADMIN'] == 1;
}
}