- 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
374 lines
13 KiB
PHP
374 lines
13 KiB
PHP
<?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;
|
|
}
|
|
}
|