diff --git a/objects/functionsAVideo.php b/objects/functionsAVideo.php index be863f5ae8..74375a17a6 100644 --- a/objects/functionsAVideo.php +++ b/objects/functionsAVideo.php @@ -6,6 +6,7 @@ $AVideoEncoder_UA = "AVideoEncoder"; $AVideoEncoderNetwork_UA = "AVideoEncoderNetwork"; $AVideoStreamer_UA = "AVideoStreamer"; $AVideoStorage_UA = "AVideoStorage"; +$AVideoRestreamer_UA = "AVideoRestreamer"; function isAVideoMobileApp($user_agent = "") { diff --git a/objects/functionsFFMPEG.php b/objects/functionsFFMPEG.php index 01bf298170..2df38172ae 100644 --- a/objects/functionsFFMPEG.php +++ b/objects/functionsFFMPEG.php @@ -570,7 +570,7 @@ function buildFFMPEGRemoteURL($actionParams) return $url; } -function execFFMPEGAsyncOrRemote($command, $keyword = null) +function execFFMPEGAsyncOrRemote($command, $keyword) { $url = buildFFMPEGRemoteURL(['ffmpegCommand' => $command, 'keyword' => $keyword]); if ($url) { @@ -586,8 +586,9 @@ function execFFMPEGAsyncOrRemote($command, $keyword = null) function getFFMPEGRemoteLog($keyword) { $url = buildFFMPEGRemoteURL(['log' => 1, 'keyword' => $keyword]); + //var_dump($url); if ($url) { - _error_log("getFFMPEGRemoteLog: URL $url"); + _error_log("getFFMPEGRemoteLog: URL $url ".json_encode(debug_backtrace())); return json_decode(url_get_contents($url)); } else { return false; diff --git a/objects/mysql_dal.php b/objects/mysql_dal.php index b642f60808..027423c84e 100644 --- a/objects/mysql_dal.php +++ b/objects/mysql_dal.php @@ -498,7 +498,7 @@ class sqlDAL global $crc, $fetchAllAssoc_cache, $isStandAlone; if($isStandAlone){ - return false; + return array(); } if (!isset($fetchAllAssoc_cache)) { $fetchAllAssoc_cache = []; diff --git a/objects/playlist.php b/objects/playlist.php index 68431d0b94..d9cafc4ba8 100644 --- a/objects/playlist.php +++ b/objects/playlist.php @@ -1338,6 +1338,12 @@ class PlayList extends ObjectYPT $values[] = $playlists_id; } + if (!empty($users_id)) { + $sql .= " AND pl.users_id = ? "; + $formats .= "i"; + $values[] = $users_id; + } + if (!empty($status)) { $sql .= " AND status = ? "; $formats .= "s"; diff --git a/objects/playlistsPublic.json.php b/objects/playlistsPublic.json.php index ab2a545bcc..b7dfa57a02 100644 --- a/objects/playlistsPublic.json.php +++ b/objects/playlistsPublic.json.php @@ -11,6 +11,6 @@ header('Content-Type: application/json'); _session_write_close(); setRowCount(10); //mysqlBeginTransaction(); -$row = PlayList::getAll('public', @$_REQUEST['Playlists_id']); +$row = PlayList::getAll('public', @$_REQUEST['Playlists_id'], (User::isAdmin()?0:USer::getId())); //mysqlCommit(); echo json_encode($row); diff --git a/plugin/API/standAlone/ffmpeg.json.php b/plugin/API/standAlone/ffmpeg.json.php index b8c1466ea0..ec5059521a 100644 --- a/plugin/API/standAlone/ffmpeg.json.php +++ b/plugin/API/standAlone/ffmpeg.json.php @@ -103,7 +103,7 @@ _error_log("Script initiated: FFMPEG command execution script started"); if (empty($streamerURL)) { _error_log("Error: streamerURL is not defined"); - echo json_encode(['error' => true, 'message' => 'streamerURL not defined']); + echo json_encode(['error' => true, 'msg' => 'streamerURL not defined']); exit; } @@ -124,6 +124,7 @@ function _decryptString($string) } } _error_log("Failed to decrypt string or invalid time"); + //return $json2; return false; } @@ -154,8 +155,9 @@ function sanitizeFFmpegCommand($command) // Remove dangerous characters $command = str_replace('&&', '', $command); - $command = str_replace('rtmp://live/', 'rtmp://vlu.me/', $command); - $command = str_replace('https://live:8443/', 'https://vlu.me:8443/', $command); + $command = str_replace('rtmp://vlu.me/', 'rtmp://live/', $command); + //$command = str_replace('rtmp://live/', 'rtmp://vlu.me/', $command); + //$command = str_replace('https://live:8443/', 'https://vlu.me:8443/', $command); $command = preg_replace('/\s*>.*(?:2>&1)?/', '', $command); $command = preg_replace('/[;|`<>]/', '', $command); @@ -171,34 +173,58 @@ function sanitizeFFmpegCommand($command) return ''; } +function addKeywordToFFmpegCommand(string $command, string $keyword): string { + // Escape the keyword to avoid shell injection + $escapedKeyword = escapeshellarg($keyword); + + // Break the command into parts to safely insert the metadata + $commandParts = explode(' ', $command); + + // Find the index of the output URL (typically the last argument in FFmpeg commands) + $outputUrlIndex = array_key_last($commandParts); + if (preg_match('/^(rtmp|http|https):\/\//', $commandParts[$outputUrlIndex])) { + // Insert metadata before the output URL + array_splice($commandParts, $outputUrlIndex, 0, ["-metadata", "keyword=$escapedKeyword"]); + } else { + // If no URL is found, append metadata at the end + $commandParts[] = "-metadata"; + $commandParts[] = "keyword=$escapedKeyword"; + } + + // Reconstruct the command + return implode(' ', $commandParts); +} + _error_log("Fetching inputs..."); $codeToExecEncrypted = getInput('codeToExecEncrypted', ''); $codeToExec = _decryptString($codeToExecEncrypted); if (empty($codeToExec)) { _error_log("Invalid or missing codeToExecEncrypted"); - die('Invalid Request'); + die(json_encode(array('error' => true, 'msg' => 'Invalid or missing code'))); } $ffmpegCommand = !empty($codeToExec->ffmpegCommand) ? sanitizeFFmpegCommand($codeToExec->ffmpegCommand) : ''; -$keyword = !empty($codeToExec->keyword) ? preg_replace('/[^a-zA-Z0-9_-]/', '', $codeToExec->keyword) : date('Ymdhmi'); + +if (empty($codeToExec->keyword)) { + _error_log("keyword: is empty"); + $keyword = date('Ymdhmi'); +} else { + _error_log("keyword: found: {$codeToExec->keyword}"); + $keyword = preg_replace('/[^a-zA-Z0-9_-]/', '', $codeToExec->keyword); +} _error_log("Code to Execute: " . json_encode($codeToExec)); _error_log("Sanitized FFMPEG Command: $ffmpegCommand"); _error_log("Keyword: $keyword"); -// Kill processes associated with the keyword -if (!empty($keyword)) { - _error_log("Killing process with keyword: $keyword"); - killProcessFromKeyword($keyword); -} $tempDir = "{$global['systemRootPath']}videos/ffmpegLogs/"; make_path($tempDir); $tempDir = rtrim($tempDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; $logFile = "{$tempDir}ffmpeg_{$keyword}.log"; -_error_log("Log file set to: $logFile"); +//_error_log("Log file set to: $logFile"); if (!empty($codeToExec->test)) { $microtime = microtime(true); @@ -229,13 +255,36 @@ if (!empty($codeToExec->test)) { exit; } else if (!empty($codeToExec->stop) && !empty($keyword)) { _error_log("Stop mode triggered for keyword: $keyword"); + + // Count the number of processes with the keyword before killing them + $countCommand = "pgrep -f 'ffmpeg.*$keyword' | wc -l"; + $processToKillCount = intval(exec($countCommand)); + + // Kill the processes matching the keyword + $killCommand = "pkill -f 'ffmpeg.*$keyword'"; + $killResult = exec($killCommand, $output, $killStatus); + + $processAfterKillCount = intval(exec($countCommand)); + + // Attempt to delete the log file + $unlinkSuccess = false; + if (file_exists($logFile)) { + $unlinkSuccess = unlink($logFile); + } + echo json_encode([ - 'error' => !file_exists($logFile), - 'msg' => '', + 'error' =>$processAfterKillCount !== 0, // Indicate if killing processes was successful + 'msg' => $processAfterKillCount !== 0 ? 'Processes killed successfully.' : 'Failed to kill processes.', 'logFile' => $logFile, - 'kill' => exec("pkill -f 'ffmpeg.*$keyword'"), + 'kill' => $killResult, // Result of pkill command 'keyword' => $keyword, - 'unlink' => unlink($logFile), + 'unlink' => $unlinkSuccess, + 'processToKillCount' => $processToKillCount, + 'processAfterKillCount' => $processAfterKillCount, // Number of processes killed + 'countCommand' => $countCommand, + 'killCommand' => $killCommand, + 'output' => $output, + 'killStatus' => $killStatus, ]); exit; } @@ -250,6 +299,16 @@ if (empty($ffmpegCommand)) { exit; } +// Kill processes associated with the keyword +if (!empty($keyword)) { + _error_log("Killing process with keyword: $keyword"); + killProcessFromKeyword($keyword); +} + +$ffmpegCommand = addKeywordToFFmpegCommand($ffmpegCommand, $keyword); + +file_put_contents($logFile, $ffmpegCommand.PHP_EOL.PHP_EOL); + $ffmpegCommand .= " > {$logFile} 2>&1"; _error_log("Executing FFMPEG Command [$keyword]: $ffmpegCommand"); diff --git a/plugin/Live/Objects/Live_restreams_logs.php b/plugin/Live/Objects/Live_restreams_logs.php index 9536bae061..a18bcc6d2a 100644 --- a/plugin/Live/Objects/Live_restreams_logs.php +++ b/plugin/Live/Objects/Live_restreams_logs.php @@ -150,6 +150,7 @@ class Live_restreams_logs extends ObjectYPT } else { $live_restreams_logs_id = $latest['id']; } + //var_dump($live_transmitions_history_id, $live_restreams_id, $live_restreams_logs_id, $action); return self::getURL($live_transmitions_history_id, $live_restreams_id, $live_restreams_logs_id, $action); } diff --git a/plugin/Live/standAloneFiles/kill_ffmpeg_restream.php b/plugin/Live/standAloneFiles/kill_ffmpeg_restream.php index 0b7975e81d..0ee5f12508 100644 --- a/plugin/Live/standAloneFiles/kill_ffmpeg_restream.php +++ b/plugin/Live/standAloneFiles/kill_ffmpeg_restream.php @@ -87,7 +87,7 @@ foreach ($logFiles as $logFile) { echo "kill_ffmpeg_restream.php The file too large logFiles $logFile "._humanFileSize($filesize).PHP_EOL; continue; }else{ - echo "kill_ffmpeg_restream.php logFiles $logFile "._humanFileSize($filesize).PHP_EOL; + //echo "kill_ffmpeg_restream.php logFiles $logFile "._humanFileSize($filesize).PHP_EOL; } $lastModifiedFormatted = formatLastModifiedTime($lastModified); @@ -114,7 +114,7 @@ foreach ($logFiles as $logFile) { $lastUrlOpened = ''; $foundTsFile = false; - echo "kill_ffmpeg_restream.php start.\n"; + //echo "kill_ffmpeg_restream.php start.\n"; // Loop through the last N lines of the log file foreach ($logContent as $key => $line) { $line = str_replace(array("\r", "\n"), '', $line); @@ -151,7 +151,7 @@ foreach ($logFiles as $logFile) { } } } - echo "kill_ffmpeg_restream.php done.\n"; + //echo "kill_ffmpeg_restream.php done.\n"; // If any .ts file is found, do not kill the process if ($foundTsFile) { echo "Found .ts file in log, process will not be killed for log file: $logFile (last modified on $lastModifiedFormatted).\n"; diff --git a/plugin/Live/standAloneFiles/restreamer.json.php b/plugin/Live/standAloneFiles/restreamer.json.php index afc65ff6d4..ab73bcfdae 100644 --- a/plugin/Live/standAloneFiles/restreamer.json.php +++ b/plugin/Live/standAloneFiles/restreamer.json.php @@ -77,6 +77,8 @@ if (!empty($_REQUEST['tokenForAction'])) { $json = verifyTokenForAction($_REQUEST['tokenForAction']); //var_dump($json);exit; if (!empty($json) && isset($json->error) && empty($json->error)) { + $keyword = 'restream_' . md5(basename($json->logFile)); + $obj->keyword = $keyword; $obj->error = false; error_log("Restreamer.json.php token verified " . json_encode($json)); switch ($json->action) { @@ -85,15 +87,14 @@ if (!empty($_REQUEST['tokenForAction'])) { $obj->logName = str_replace($logFileLocation, '', $json->logFile); $obj->logName = preg_replace('/[^a-z0-9_.-]/i', '', $obj->logName); - $keyword ='restream_'. md5($json->logFile); $resp = getFFMPEGRemoteLog($keyword); - if(!empty($resp) && empty($resp->error)){ + if (!empty($resp) && empty($resp->error)) { $obj->modified = $resp->modified; $obj->secondsAgo = $resp->secondsAgo; $obj->isActive = $resp->isActive; $obj->remoteLog = true; $obj->resp = $resp; - }else if (!empty($obj->logName)) { + } else if (!empty($obj->logName)) { $logFile = $logFileLocation . $obj->logName; if (file_exists($logFile)) { $obj->modified = @filemtime($logFile); @@ -107,11 +108,20 @@ if (!empty($_REQUEST['tokenForAction'])) { exit; break; case 'stop': - $obj->killIfIsRunning = killIfIsRunning($json); - $obj->logName = str_replace($logFileLocation, '', $json->logFile); - $obj->logName = preg_replace('/[^a-z0-9_.-]/i', '', $obj->logName); - $logFile = $logFileLocation . $obj->logName; - unlink($logFile); + + $resp = stopFFMPEGRemote($keyword); + $obj->remoteResponse = $resp; + if (!empty($resp) && empty($resp->error)) { + $obj->remoteKill = true; + }else{ + $obj->killIfIsRunning = killIfIsRunning($json); + $obj->logName = str_replace($logFileLocation, '', $json->logFile); + $obj->logName = preg_replace('/[^a-z0-9_.-]/i', '', $obj->logName); + $logFile = $logFileLocation . $obj->logName; + $obj->remoteKill = false; + unlink($logFile); + } + echo json_encode($obj); exit; break; @@ -646,9 +656,10 @@ function startRestream($m3u8, $restreamsDestinations, $logFile, $robj, $tries = _make_path($logFile); file_put_contents($logFile, $command . PHP_EOL); if (empty($isATest)) { - $keyword = md5($logFile); + $keyword = 'restream_' . md5(basename($logFile)); + $robj->keyword = $keyword; // use remote ffmpeg here - execFFMPEGAsyncOrRemote($command . ' > ' . $logFile, 'restream_'.$keyword); + execFFMPEGAsyncOrRemote($command . ' > ' . $logFile, $keyword); } error_log("Restreamer.json.php startRestream finish"); } diff --git a/plugin/Live/view/getRestream.json.php b/plugin/Live/view/getRestream.json.php index db59b71fb5..b64e078477 100644 --- a/plugin/Live/view/getRestream.json.php +++ b/plugin/Live/view/getRestream.json.php @@ -57,7 +57,7 @@ if (empty($obj->url)) { } debugLog(__LINE__); -unset($obj->url); +//unset($obj->url); $obj->end = number_format(microtime(true) - $obj->start, 2); die(json_encode($obj)); diff --git a/plugin/Scheduler/Scheduler.php b/plugin/Scheduler/Scheduler.php index a7f81f82f8..3eab612e4a 100644 --- a/plugin/Scheduler/Scheduler.php +++ b/plugin/Scheduler/Scheduler.php @@ -585,10 +585,9 @@ class Scheduler extends PluginAbstract // Run the function to delete files older than 7 days from /var/www/tmp $this->deleteOldFiles(); + self::manageLogFile(); } - - function deleteOldFiles($directory = '/var/www/tmp', $days = 7) { // Check if the directory exists @@ -639,4 +638,69 @@ class Scheduler extends PluginAbstract return true; } + + static function manageLogFile() + { + global $global; + $logFilePath = $global['logfile']; + + // Ensure the logfile is not empty and has a .log extension + if (empty($logFilePath)) { + _error_log("Log file path is empty; no action required."); + return; + } + + if (pathinfo($logFilePath, PATHINFO_EXTENSION) !== 'log') { + _error_log("Log file path is not a .log file; no action required. [$logFilePath]"); + return; + } + + // Get yesterday's date + $yesterdayDate = date('Y-m-d', strtotime('-1 day')); + + // Define the new logfile name with yesterday's date + $newLogFileName = $logFilePath . '.' . $yesterdayDate . '.log'; + + // Check if the current logfile exists + if (file_exists($logFilePath)) { + // Move the current logfile to a new file with yesterday's date + if (rename($logFilePath, $newLogFileName)) { + _error_log("Log file successfully moved to: $newLogFileName"); + } else { + _error_log("Failed to move log file to: $newLogFileName"); + } + } else { + _error_log("Log file does not exist: $logFilePath"); + } + + // Create a new empty logfile + if (touch($logFilePath)) { + _error_log("New log file created: $logFilePath"); + + // Ensure Apache can write to the new log file + if (chmod($logFilePath, 0666)) { + _error_log("Permissions set to 0666 for: $logFilePath"); + } else { + _error_log("Failed to set permissions for: $logFilePath"); + } + } else { + _error_log("Failed to create new log file: $logFilePath"); + } + + // Delete log files older than 30 days in the same directory + $logDir = dirname($logFilePath); + $files = glob($logDir . '/*.log'); // Get all .log files in the directory + + $thirtyDaysAgo = time() - (30 * 24 * 60 * 60); // Timestamp for 30 days ago + + foreach ($files as $file) { + if (filemtime($file) < $thirtyDaysAgo) { + if (unlink($file)) { + _error_log("Deleted old log file: $file"); + } else { + _error_log("Failed to delete old log file: $file"); + } + } + } + } }