first commit

This commit is contained in:
Devdatt Bhatt 2026-06-04 08:28:08 +00:00
commit d2db6d0376
26 changed files with 8931 additions and 0 deletions

99
Readme.md Normal file
View File

@ -0,0 +1,99 @@
Urmi Downloader / Converter
----------------------------------------
Yt-dlp webui
Samba share to access
FFmpeg to convert files
Instalation
----------------------------------------
Install Ubuntu server 24.04 and run following commands as root
cd /tmp
apt install git -y
cd /tmp
git clone --depth 1 https://git.dbhatt.org/hw_partner/urmic_digital_encoder_decoder.git
cd urmic_digital_encoder_decoder
chmod +x install.sh
./install.sh
after reboot visit http://IP
default Username :- shreebhattji
default password :- foreverstreamingpartner
Under Licence https://github.com/shreebhattji/Urmi
Urmi an "you happy me happy" license
----------------------------------------
In the name , memory and honor of
Every single mother who given birth , love , part of it self to their child ,
Every single father who fulfilled his every single duty ,
Every single teacher who taught human and made civilization possible ,
Every single warrior who stood still and took a fall on own self for protection of other ,
Every single savior who risked their life own life to save others ,
Every single power who helped individuals in the greatest hour of need ,
Every single artist who made human life happy , pleasant and colorful ,
Every single human who ever walked and contributed to constructive work of humanity.
Every single Power ever existed in universe who done good
This is given to every single human , goverment , NGO , educational institutes .
If you use things under this licence you have to spend 11$ or more (US dollar) for one of the following in ascending order.
1 > If you have a sister, hand over 11$ or more to her .
2 > If you have a loan on you, repay 11$ or more extra .
3 > If your parents ever taken care of you, hand over 11$ or more to them .
4 > If any relative ever helped you , find some family members in need and send them 11$ or more.
5 > If you ever enjoyed any service from your state, donate 11$ or more to your state .
6 > If you are protected in your country, donate 11$ or more to your country .
7 > we came empty handed and we go empty handed from earth, however while on earth if you ever got help please help others .
Distribution Rules
----------------------------------------------------------------------
work published under this licence can be obtained by any entity .
work published under this licence is permenent and only share able with same licene , selling ,leasing reselling or doing any thing else is illigal .
What about me ... ??? hmmmmmmm
-----------------------------------
To achieve any goal in life anyone need to have two main things "GOOD KARMA" and "BLESSINGS" ,
I am taking care of good karma ,now providing blessing is gods responsibility .
We all have only one life , death is only truth of our life.
I wish every soul a life with good health ,happy family ,mighty power , highest respect and unlimited fortune.
- With faith and hope
Notice :- We do not accept any kind of donation in any way in any manner.
If you dont spend 11$ or more after seeing this email god will transfer all of your good KARMA to my account so dont forgot to spend 11$ or more according to licence.

208
downloader/class/Downloader.php Executable file
View File

@ -0,0 +1,208 @@
<?php
include_once('FileHandler.php');
class Downloader
{
private $urls = [];
private $config = [];
private $audio_only = false;
private $errors = [];
private $download_path = "";
public function __construct($post, $audio_only)
{
$this->config = require dirname(__DIR__).'/config/config.php';
$this->download_path = (new FileHandler())->get_downloads_folder();
$this->audio_only = $audio_only;
$this->urls = explode(",", $post);
if(!$this->check_requirements($audio_only))
{
return;
}
foreach ($this->urls as $url)
{
if(!$this->is_valid_url($url))
{
$this->errors[] = "\"".$url."\" is not a valid url !";
}
}
if(isset($this->errors) && count($this->errors) > 0)
{
$_SESSION['errors'] = $this->errors;
return;
}
if($this->config["max_dl"] == 0)
{
$this->do_download();
}
elseif($this->config["max_dl"] > 0)
{
if($this->background_jobs() >= 0 && $this->background_jobs() < $this->config["max_dl"])
{
$this->do_download();
}
else
{
$this->errors[] = "Simultaneous downloads limit reached !";
}
}
if(isset($this->errors) && count($this->errors) > 0)
{
$_SESSION['errors'] = $this->errors;
return;
}
}
public static function background_jobs()
{
return shell_exec("ps aux | grep -v grep | grep -v \"yt-dlp -U\" | grep yt-dlp | wc -l");
}
public static function max_background_jobs()
{
$config = require dirname(__DIR__).'/config/config.php';
return $config["max_dl"];
}
public static function get_current_background_jobs()
{
exec("ps -A -o user,pid,etime,cmd | grep -v grep | grep -v \"yt-dlp -U\" | grep yt-dlp", $output);
$bjs = [];
if(count($output) > 0)
{
foreach($output as $line)
{
$line = explode(' ', preg_replace ("/ +/", " ", $line), 4);
$bjs[] = array(
'user' => $line[0],
'pid' => $line[1],
'time' => $line[2],
'cmd' => $line[3]
);
}
return $bjs;
}
else
{
return null;
}
}
public static function kill_them_all()
{
exec("ps -A -o pid,cmd | grep -v grep | grep yt-dlp | awk '{print $1}'", $output);
if(count($output) <= 0)
return;
foreach($output as $p)
{
shell_exec("kill ".$p);
}
$config = require dirname(__DIR__).'/config/config.php';
$folder = $this->download_path;
foreach(glob($folder.'*.part') as $file)
{
unlink($file);
}
}
private function check_requirements($audio_only)
{
if($this->is_youtubedl_installed() != 0)
{
$this->errors[] = "yt-dlp is not installed, see <a>https://rg3.github.io/yt-dlp/download.html</a> !";
}
$this->check_outuput_folder();
if($audio_only)
{
if($this->is_extracter_installed() != 0)
{
$this->errors[] = "Install an audio extracter (ex: avconv) !";
}
}
if(isset($this->errors) && count($this->errors) > 0)
{
$_SESSION['errors'] = $this->errors;
return false;
}
return true;
}
private function is_youtubedl_installed()
{
exec("which yt-dlp", $out, $r);
return $r;
}
private function is_extracter_installed()
{
exec("which ".$this->config["extracter"], $out, $r);
return $r;
}
private function is_valid_url($url)
{
return filter_var($url, FILTER_VALIDATE_URL);
}
private function check_outuput_folder()
{
if(!is_dir($this->download_path))
{
//Folder doesn't exist
if(!mkdir($this->download_path, 0775))
{
$this->errors[] = "Output folder doesn't exist and creation failed !";
}
}
else
{
//Exists but can I write ?
if(!is_writable($this->download_path))
{
$this->errors[] = "Output folder isn't writable !";
}
}
}
private function do_download()
{
$cmd = "yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio' --merge-output-format mp4";
$cmd .= " -o ".$this->download_path."/";
$cmd .= escapeshellarg("%(title)s.%(ext)s");
if($this->audio_only)
{
$cmd .= " -x --audio-format mp3 ";
}
foreach($this->urls as $url)
{
$cmd .= " ".escapeshellarg($url);
}
$cmd .= " --restrict-filenames"; // --restrict-filenames is for specials chars
$cmd .= " > /dev/null & echo $!";
shell_exec($cmd);
}
}
?>

121
downloader/class/FileHandler.php Executable file
View File

@ -0,0 +1,121 @@
<?php
class FileHandler
{
private $config = [];
private $videos_ext = ".{avi,mp4,flv,webm,mkv}";
private $musics_ext = ".{mp3,ogg,m4a,opus}";
public function __construct()
{
$this->config = require dirname(__DIR__).'/config/config.php';
}
public function listVideos()
{
$videos = [];
if(!$this->outuput_folder_exists())
return;
$folder = $this->get_downloads_folder().'/';
foreach(glob($folder.'*'.$this->videos_ext, GLOB_BRACE) as $file)
{
$video = [];
$video["name"] = str_replace($folder, "", $file);
$video["size"] = $this->to_human_filesize(filesize($file));
$videos[] = $video;
}
return $videos;
}
public function listMusics()
{
$musics = [];
if(!$this->outuput_folder_exists())
return;
$folder = $this->get_downloads_folder().'/';
foreach(glob($folder.'*'.$this->musics_ext, GLOB_BRACE) as $file)
{
$music = [];
$music["name"] = str_replace($folder, "", $file);
$music["size"] = $this->to_human_filesize(filesize($file));
$musics[] = $music;
}
return $musics;
}
public function delete($id, $type)
{
$folder = $this->get_downloads_folder().'/';
$i = 0;
if($type === 'v')
{
$exts = $this->videos_ext;
}
elseif($type === 'm')
{
$exts = $this->musics_ext;
}
else
{
return;
}
foreach(glob($folder.'*'.$exts, GLOB_BRACE) as $file)
{
if($i == $id)
{
unlink($file);
}
$i++;
}
}
private function outuput_folder_exists()
{
if(!is_dir($this->get_downloads_folder()))
{
//Folder doesn't exist
if(!mkdir($this->get_downloads_folder(),0777))
{
return false; //No folder and creation failed
}
}
return true;
}
public function to_human_filesize($bytes, $decimals = 0)
{
$sz = 'BKMGTP';
$factor = floor((strlen($bytes) - 1) / 3);
return sprintf("%.{$decimals}f", $bytes / pow(1024, $factor)) . @$sz[$factor];
}
public function free_space()
{
return $this->to_human_filesize(disk_free_space($this->get_downloads_folder()));
}
public function get_downloads_folder()
{
$path = $this->config["outputFolder"];
if(strpos($path , "/") !== 0)
{
$path = dirname(__DIR__).'/' . $path;
}
return $path;
}
}
?>

64
downloader/class/Session.php Executable file
View File

@ -0,0 +1,64 @@
<?php
class Session
{
private $config = [];
private static $_instance;
public function __construct()
{
session_start();
$this->config = require dirname(__DIR__).'/config/config.php';
if($this->config["security"])
{
if(!isset($_SESSION["logged_in"]))
{
$_SESSION["logged_in"] = false;
}
}
else
{
$_SESSION["logged_in"] = true;
}
}
public static function getInstance()
{
if(is_null(self::$_instance))
{
self::$_instance = new Session();
}
return self::$_instance;
}
public function login($password)
{
if($this->config["password"] === md5($password))
{
$_SESSION["logged_in"] = true;
return true;
}
else
{
$_SESSION["logged_in"] = false;
return false;
}
}
public function is_logged_in()
{
return $_SESSION["logged_in"];
}
public function logout()
{
$_SESSION = array();
session_destroy();
}
}
?>

13
downloader/config/config.php Executable file
View File

@ -0,0 +1,13 @@
<?php
// Mahadev@123 password
return array(
"security" => true,
"password" => "efd6f45f7fe2cf3a2f0bd3812b201fc0",
"outputFolder" => "/var/www/html/download",
"extracter" => "ffmpeg",
"max_dl" => 10);
?>

6970
downloader/css/bootstrap.min.css vendored Executable file

File diff suppressed because it is too large Load Diff

4
downloader/css/font-awesome.min.css vendored Executable file

File diff suppressed because one or more lines are too long

200
downloader/download.php Executable file
View File

@ -0,0 +1,200 @@
<?php
require_once 'class/Session.php';
require_once 'class/Downloader.php';
require_once 'class/FileHandler.php';
$session = Session::getInstance();
$file = new FileHandler;
if(!$session->is_logged_in())
{
header("Location: login.php");
}
###############################################################
# File Download 1.31
###############################################################
# Visit http://www.zubrag.com/scripts/ for updates
###############################################################
# Sample call:
# download.php?f=phptutorial.zip
#
# Sample call (browser will try to save with new file name):
# download.php?f=phptutorial.zip&fc=php123tutorial.zip
###############################################################
// Allow direct file download (hotlinking)?
// Empty - allow hotlinking
// If set to nonempty value (Example: example.com) will only allow downloads when referrer contains this text
define('ALLOWED_REFERRER', '');
// Download folder, i.e. folder where you keep all files for download.
// MUST end with slash (i.e. "/" )
define('BASE_DIR',$file->get_downloads_folder());
// log downloads? true/false
define('LOG_DOWNLOADS',true);
// log file name
define('LOG_FILE','downloads.log');
// Allowed extensions list in format 'extension' => 'mime type'
// If myme type is set to empty string then script will try to detect mime type
// itself, which would only work if you have Mimetype or Fileinfo extensions
// installed on server.
$allowed_ext = array (
// audio
'mp3' => 'audio/mpeg',
'wav' => 'audio/x-wav',
// video
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mkv' => 'video/mpeg',
'mp4' => 'video/mpeg',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
'webm' => 'video/mpeg'
);
####################################################################
### DO NOT CHANGE BELOW
####################################################################
// If hotlinking not allowed then make hackers think there are some server problems
if (ALLOWED_REFERRER !== ''
&& (!isset($_SERVER['HTTP_REFERER']) || strpos(strtoupper($_SERVER['HTTP_REFERER']),strtoupper(ALLOWED_REFERRER)) === false)
) {
die("Internal server error. Please contact system administrator.");
}
// Make sure program execution doesn't time out
// Set maximum script execution time in seconds (0 means no limit)
set_time_limit(0);
if (!isset($_GET['f']) || empty($_GET['f'])) {
die("Please specify file name for download.");
}
// Nullbyte hack fix
if (strpos($_GET['f'], "\0") !== FALSE) die('');
// Get real file name.
// Remove any path info to avoid hacking by adding relative path, etc.
$fname = basename($_GET['f']);
// Check if the file exists
// Check in subfolders too
function find_file ($dirname, $fname, &$file_path) {
$dir = opendir($dirname);
while ($file = readdir($dir)) {
if (empty($file_path) && $file != '.' && $file != '..') {
if (is_dir($dirname.'/'.$file)) {
find_file($dirname.'/'.$file, $fname, $file_path);
}
else {
if (file_exists($dirname.'/'.$fname)) {
$file_path = $dirname.'/'.$fname;
return;
}
}
}
}
} // find_file
// get full file path (including subfolders)
$file_path = '';
find_file(BASE_DIR, $fname, $file_path);
if (!is_file($file_path)) {
die("File does not exist. Make sure you specified correct file name.");
}
// file size in bytes
$fsize = filesize($file_path);
// file extension
$fext = strtolower(substr(strrchr($fname,"."),1));
// check if allowed extension
if (!array_key_exists($fext, $allowed_ext)) {
die("Not allowed file type.");
}
// get mime type
if ($allowed_ext[$fext] == '') {
$mtype = '';
// mime type is not set, get from server settings
if (function_exists('mime_content_type')) {
$mtype = mime_content_type($file_path);
}
else if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME); // return mime type
$mtype = finfo_file($finfo, $file_path);
finfo_close($finfo);
}
if ($mtype == '') {
$mtype = "application/force-download";
}
}
else {
// get mime type defined by admin
$mtype = $allowed_ext[$fext];
}
// Browser will try to save file with this filename, regardless original filename.
// You can override it if needed.
if (!isset($_GET['fc']) || empty($_GET['fc'])) {
$asfname = $fname;
}
else {
// remove some bad chars
$asfname = str_replace(array('"',"'",'\\','/'), '', $_GET['fc']);
if ($asfname === '') $asfname = 'NoName';
}
// set headers
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Type: $mtype");
header("Content-Disposition: attachment; filename=\"$asfname\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . $fsize);
// download
// @readfile($file_path);
$file = @fopen($file_path,"rb");
if ($file) {
while(!feof($file)) {
print(fread($file, 1024*8));
flush();
if (connection_status()!=0) {
@fclose($file);
die();
}
}
@fclose($file);
}
// log downloads
if (!LOG_DOWNLOADS) die();
$f = @fopen(LOG_FILE, 'a+');
if ($f) {
@fputs($f, date("m.d.Y g:ia")." ".$_SERVER['REMOTE_ADDR']." ".$fname."\n");
@fclose($f);
}
?>

BIN
downloader/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

BIN
downloader/img/list.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
downloader/img/main.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

91
downloader/index.php Executable file
View File

@ -0,0 +1,91 @@
<?php
require_once 'class/Session.php';
require_once 'class/Downloader.php';
require_once 'class/FileHandler.php';
$session = Session::getInstance();
$file = new FileHandler;
require 'views/header.php';
if(!$session->is_logged_in())
{
header("Location: login.php");
}
else
{
if(isset($_GET['kill']) && !empty($_GET['kill']) && $_GET['kill'] === "all")
{
Downloader::kill_them_all();
}
if(isset($_POST['urls']) && !empty($_POST['urls']))
{
$audio_only = false;
if(isset($_POST['audio']) && !empty($_POST['audio']))
{
$audio_only = true;
}
$downloader = new Downloader($_POST['urls'], $audio_only);
if(!isset($_SESSION['errors']))
{
header("Location: index.php");
}
}
}
?>
<div class="container">
<h1>Download</h1>
<?php
if(isset($_SESSION['errors']) && $_SESSION['errors'] > 0)
{
foreach ($_SESSION['errors'] as $e)
{
echo "<div class=\"alert alert-warning\" role=\"alert\">$e</div>";
}
}
?>
<form id="download-form" class="form-horizontal" action="index.php" method="post">
<div class="form-group">
<div class="col-md-10">
<input class="form-control" id="url" name="urls" placeholder="Link(s) separated by a comma" type="text">
</div>
<div class="col-md-2">
<div class="checkbox">
<label>
<input type="checkbox" name="audio"> Audio Only
</label>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">Download</button>
</form>
<br>
<div class="row">
<div class="col-lg-6">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">Info</h3></div>
<div class="panel-body">
<p>Free space : <?php echo $file->free_space(); ?></b></p>
<p>Download folder : <?php echo $file->get_downloads_folder(); ?></p>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="panel panel-info">
<div class="panel-heading"><h3 class="panel-title">Help</h3></div>
<div class="panel-body">
</div>
</div>
</div>
</div>
</div>
<?php
unset($_SESSION['errors']);
require 'views/footer.php';
?>

7
downloader/js/bootstrap.min.js vendored Executable file

File diff suppressed because one or more lines are too long

4
downloader/js/jquery-1.11.1.min.js vendored Executable file

File diff suppressed because one or more lines are too long

92
downloader/list.php Executable file
View File

@ -0,0 +1,92 @@
<?php
require_once 'class/Session.php';
require_once 'class/Downloader.php';
require_once 'class/FileHandler.php';
$session = Session::getInstance();
$file = new FileHandler;
if(!$session->is_logged_in())
{
header("Location: login.php");
}
if(isset($_GET['type']) && !empty($_GET['type']))
{
$t = $_GET['type'];
if($t === 'v')
{
$type = "videos";
$files = $file->listVideos();
}
elseif($t === 'm')
{
$type = "musics";
$files = $file->listMusics();
}
}
if($session->is_logged_in() && isset($_GET["delete"]))
{
$file->delete($_GET["delete"], $t);
header("Location: list.php?type=".$t);
}
require 'views/header.php';
?>
<div class="container">
<?php
if(!empty($files))
{
?>
<h2>List of available <?php echo $type ?> :</h2>
<table class="table table-striped table-hover ">
<thead>
<tr>
<th style="min-width:800px; height:35px">Title</th>
<th style="min-width:80px">Size</th>
<th style="min-width:80px">Play</th>
<th style="min-width:80px">Download</th>
<th style="min-width:110px">Delete link</th>
</tr>
</thead>
<tbody>
<?php
$i = 0;
$totalSize = 0;
foreach($files as $f)
{
echo "<tr>";
echo "<td>".$f["name"]."</td>";
echo "<td>".$f["size"]."</td>";
echo "<td><a href=\play.php?f=".$f["name"]." class=\"btn btn-danger btn-sm\">Play</a></td>";
echo "<td><a href=\download.php?f=".$f["name"]." class=\"btn btn-danger btn-sm\">Download</a></td>";
echo "<td><a href=\"./list.php?delete=$i&type=$t\" class=\"btn btn-danger btn-sm\">Delete</a></td>";
echo "</tr>";
$i++;
}
?>
</tbody>
</table>
<br/>
<br/>
<?php
}
else
{
if(isset($t) && ($t === 'v' || $t === 'm'))
{
echo "<br><div class=\"alert alert-warning\" role=\"alert\">No $type !</div>";
}
else
{
echo "<br><div class=\"alert alert-warning\" role=\"alert\">No such type !</div>";
}
}
?>
<br/>
</div><!-- End container -->
<?php
require 'views/footer.php';
?>

48
downloader/login.php Executable file
View File

@ -0,0 +1,48 @@
<?php
require_once 'class/Session.php';
require_once 'class/Downloader.php';
$session = Session::getInstance();
$loginError = "";
if(isset($_POST["password"]))
{
if($session->login($_POST["password"]))
{
header("Location: index.php");
}
else
{
$loginError = "Wrong password !";
}
}
?>
<?php require 'views/header.php'; ?>
<div class="container">
<?php
if($loginError !== "")
{
?>
<div class="alert alert-danger" role="alert"><?php echo $loginError; ?></div>
<?php
}
?>
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4">
<h2>Login :</h2>
</div>
<div class="col-md-4"></div>
</div>
<form class="form-horizontal" action="login.php" method="POST">
<div class="form-group">
<div class="col-lg-4"></div>
<div class="col-lg-4">
<input class="form-control" id="password" name="password" placeholder="Password" type="password">
</div>
<div class="col-lg-4"></div>
</div>
</form>
</div><!-- End container -->
<?php require 'views/footer.php'; ?>

5
downloader/logout.php Executable file
View File

@ -0,0 +1,5 @@
<?php
require 'class/Session.php';
Session::getInstance()->logout();
header("Location: index.php");
?>

188
downloader/play.php Executable file
View File

@ -0,0 +1,188 @@
<?php
require_once 'class/Session.php';
require_once 'class/Downloader.php';
require_once 'class/FileHandler.php';
$session = Session::getInstance();
$file = new FileHandler;
if(!$session->is_logged_in())
{
header("Location: login.php");
}
###############################################################
# File Download 1.31
###############################################################
# Visit http://www.zubrag.com/scripts/ for updates
###############################################################
# Sample call:
# download.php?f=phptutorial.zip
#
# Sample call (browser will try to save with new file name):
# download.php?f=phptutorial.zip&fc=php123tutorial.zip
###############################################################
// Allow direct file download (hotlinking)?
// Empty - allow hotlinking
// If set to nonempty value (Example: example.com) will only allow downloads when referrer contains this text
define('ALLOWED_REFERRER', '');
// Download folder, i.e. folder where you keep all files for download.
// MUST end with slash (i.e. "/" )
//BASE_DIR = $file->get_downloads_folder();
define('BASE_DIR',$file->get_downloads_folder());
// log plays? true/false
define('LOG_PLAYS',true);
// log file name
define('LOG_FILE','plays.log');
// Allowed extensions list in format 'extension' => 'mime type'
// If myme type is set to empty string then script will try to detect mime type
// itself, which would only work if you have Mimetype or Fileinfo extensions
// installed on server.
$allowed_ext = array (
// audio
'mp3' => 'audio/mpeg',
'wav' => 'audio/x-wav',
// video
'mpeg' => 'video/mpeg',
'mp4' => 'video/mp4',
'mpg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mkv' => 'video/mpeg',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
'webm' => 'video/mpeg'
);
####################################################################
### DO NOT CHANGE BELOW
####################################################################
// If hotlinking not allowed then make hackers think there are some server problems
if (ALLOWED_REFERRER !== ''
&& (!isset($_SERVER['HTTP_REFERER']) || strpos(strtoupper($_SERVER['HTTP_REFERER']),strtoupper(ALLOWED_REFERRER)) === false)
) {
die("Internal server error. Please contact system administrator.");
}
// Make sure program execution doesn't time out
// Set maximum script execution time in seconds (0 means no limit)
set_time_limit(0);
if (!isset($_GET['f']) || empty($_GET['f'])) {
die("Please specify file name for download.");
}
// Nullbyte hack fix
if (strpos($_GET['f'], "\0") !== FALSE) die('');
// Get real file name.
// Remove any path info to avoid hacking by adding relative path, etc.
$fname = basename($_GET['f']);
// Check if the file exists
// Check in subfolders too
function find_file ($dirname, $fname, &$file_path) {
$dir = opendir($dirname);
while ($file = readdir($dir)) {
if (empty($file_path) && $file != '.' && $file != '..') {
if (is_dir($dirname.'/'.$file)) {
find_file($dirname.'/'.$file, $fname, $file_path);
}
else {
if (file_exists($dirname.'/'.$fname)) {
$file_path = $dirname.'/'.$fname;
return;
}
}
}
}
} // find_file
// get full file path (including subfolders)
$file_path = '';
find_file(BASE_DIR, $fname, $file_path);
if (!is_file($file_path)) {
die("File does not exist. Make sure you specified correct file name.");
}
// file size in bytes
$fsize = filesize($file_path);
// file extension
$fext = strtolower(substr(strrchr($fname,"."),1));
// check if allowed extension
if (!array_key_exists($fext, $allowed_ext)) {
die("Not allowed file type.");
}
// get mime type
if ($allowed_ext[$fext] == '') {
$mtype = '';
// mime type is not set, get from server settings
if (function_exists('mime_content_type')) {
$mtype = mime_content_type($file_path);
}
else if (function_exists('finfo_file')) {
$finfo = finfo_open(FILEINFO_MIME); // return mime type
$mtype = finfo_file($finfo, $file_path);
finfo_close($finfo);
}
if ($mtype == '') {
$mtype = "application/force-download";
}
}
else {
// get mime type defined by admin
$mtype = $allowed_ext[$fext];
}
// Browser will try to save file with this filename, regardless original filename.
// You can override it if needed.
if (!isset($_GET['fc']) || empty($_GET['fc'])) {
$asfname = $fname;
}
else {
// remove some bad chars
$asfname = str_replace(array('"',"'",'\\','/'), '', $_GET['fc']);
if ($asfname === '') $asfname = 'NoName';
}
// set headers
header("Content-Type:video/".$fext);
header("Content-Length: " . filesize($file_path));
header("Cache-Control:no-cache");
header("Content-Transfer-Encoding:binary");
readfile($file_path);
// log plays
if (!LOG_PLAYS) die();
$f = @fopen(LOG_FILE, 'a+');
if ($f) {
@fputs($f, date("m.d.Y g:ia")." ".$_SERVER['REMOTE_ADDR']." ".$fname."\n");
@fclose($f);
}
?>

0
downloader/plays.log Executable file
View File

1
downloader/robots.txt Executable file
View File

@ -0,0 +1 @@
User-agent: *

14
downloader/views/footer.php Executable file
View File

@ -0,0 +1,14 @@
<footer class="footer">
<div class="well text-center">
<p><a href="https://github.com/shreebhattji/Urmi">Urmi Licence</a> - <a href="https://urmic.org/">Made With love from ShreeBhattji</a></p>
</div>
</footer>
<script type="text/javascript" src="js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#url").focus();
});
</script>
</body>
</html>

71
downloader/views/header.php Executable file
View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Youtube-dl WebUI</title>
<link rel="stylesheet" href="css/bootstrap.min.css" media="screen">
<link rel="stylesheet" href="css/font-awesome.min.css">
</head>
<body>
<div class="navbar navbar-default">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="">Youtube-dl</a>
</div>
<div class="navbar-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav">
<li><a href="./">Download</a></li>
<li><a href="./list.php?type=v">List of videos</a></li>
<li><a href="./list.php?type=m">List of songs</a></li>
<?php
if($session->is_logged_in())
{
?>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
<?php if(Downloader::background_jobs() > 0) echo "<b>"; ?>Background downloads : <?php echo Downloader::background_jobs()." / ".Downloader::max_background_jobs(); if(Downloader::background_jobs() > 0) echo "</b>"; ?> <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<?php
if(Downloader::get_current_background_jobs() != null)
{
foreach(Downloader::get_current_background_jobs() as $key)
{
if (strpos($key['cmd'], '-x') !== false) //Music
{
echo "<li><a href=\"#\"><i class=\"fa fa-music\"></i> Elapsed time : ".$key['time']."</a></li>";
}
else
{
echo "<li><a href=\"#\"><i class=\"fa fa-video-camera\"></i> Elapsed time : ".$key['time']."</a></li>";
}
}
echo "<li class=\"divider\"></li>";
echo "<li><a href=\"./index.php?kill=all\">Kill all downloads</a></li>";
}
else
{
echo "<li><a>No jobs !</a></li>";
}
?>
</ul>
</li>
<?php
}
?>
</ul>
<ul class="nav navbar-nav navbar-right">
<?php
if($session->is_logged_in())
{
echo "<li><a href=\"./logout.php\">Logout</a></li>";
}
?>
</ul>
</div>
</div>

586
settings/index.php Executable file
View File

@ -0,0 +1,586 @@
<?php
// settings/index.php
// Start session to store settings
session_start();
// Define settings file path - save in same directory as this file
$settings_file = __DIR__ . '/settings.json';
// Default settings
$default_settings = [
'video_format' => 'h264',
'audio_format' => 'mp3',
'video_bitrate' => '1000k',
'audio_bitrate' => '128k',
'resolution' => '1280x720',
'frame_rate' => '30',
'gop' => '30',
];
// Initialize settings from JSON file or use defaults
if (file_exists($settings_file)) {
$settings_json = file_get_contents($settings_file);
$settings = json_decode($settings_json, true);
// Merge with defaults if any keys are missing
foreach ($default_settings as $key => $value) {
if (!isset($settings[$key])) {
$settings[$key] = $value;
}
}
} else {
$settings = $default_settings;
}
// Handle form submission
$message = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Validate video format
$allowed_video_formats = ['mpeg2', 'h264', 'h265', 'vp9', 'av1'];
if (isset($_POST['video_format']) && in_array($_POST['video_format'], $allowed_video_formats)) {
$settings['video_format'] = $_POST['video_format'];
}
// Validate audio format
$allowed_audio_formats = ['mp2', 'mp3', 'aac'];
if (isset($_POST['audio_format']) && in_array($_POST['audio_format'], $allowed_audio_formats)) {
$settings['audio_format'] = $_POST['audio_format'];
}
// Update other settings
$settings['video_bitrate'] = $_POST['video_bitrate'] ?? $settings['video_bitrate'];
$settings['audio_bitrate'] = $_POST['audio_bitrate'] ?? $settings['audio_bitrate'];
$settings['resolution'] = $_POST['resolution'] ?? $settings['resolution'];
$settings['frame_rate'] = $_POST['frame_rate'] ?? $settings['frame_rate'];
$settings['gop'] = $_POST['gop'] ?? $settings['gop'];
// Save settings to JSON file
$settings_json = json_encode($settings, JSON_PRETTY_PRINT);
if (file_put_contents($settings_file, $settings_json) !== false) {
$message = "Settings saved successfully!";
} else {
$message = "Error saving settings!";
}
}
// Save settings to session as backup
$_SESSION['settings'] = $settings;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Settings</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #0f2027, #203a43, #2c5364);
color: #fff;
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
max-width: 800px;
width: 100%;
background: rgba(13, 19, 33, 0.85);
backdrop-filter: blur(12px);
border-radius: 20px;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
overflow: hidden;
border: 1px solid rgba(92, 107, 192, 0.3);
transform: translateZ(0);
position: relative;
}
.container::before {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #1a2a6c, #2a5298, #4facfe, #00f2fe, #1a2a6c);
z-index: -1;
border-radius: 22px;
animation: gradient 15s ease infinite;
background-size: 400% 400%;
}
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.header {
background: linear-gradient(90deg, #1a2a6c, #2a5298);
padding: 30px;
text-align: center;
position: relative;
border-bottom: 2px solid rgba(92, 107, 192, 0.5);
transform: translateZ(0);
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
background: linear-gradient(to right, #4facfe, #00f2fe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
position: relative;
display: inline-block;
}
.header h1::after {
content: '';
position: absolute;
bottom: -5px;
left: 50%;
transform: translateX(-50%);
width: 80%;
height: 2px;
background: linear-gradient(to right, transparent, #4facfe, #00f2fe, transparent);
border-radius: 50%;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
margin-top: 10px;
}
.content {
padding: 30px;
}
.form-group {
margin-bottom: 25px;
position: relative;
transform: translateZ(0);
}
.form-group label {
display: block;
margin-bottom: 10px;
font-weight: 600;
font-size: 1.1rem;
color: #4facfe;
display: flex;
align-items: center;
gap: 10px;
position: relative;
/* Fix for label visibility */
z-index: 2;
}
.form-group label::before {
content: '';
position: absolute;
left: -25px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
border-radius: 50%;
background: #4facfe;
box-shadow: 0 0 10px rgba(79, 172, 254, 0.7);
z-index: 1;
}
input, select {
width: 100%;
padding: 15px;
border: 2px solid rgba(92, 107, 192, 0.5);
border-radius: 12px;
background: rgba(13, 19, 33, 0.7);
color: #fff;
font-size: 1rem;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(5px);
position: relative;
z-index: 1;
}
input:focus, select:focus {
outline: none;
border-color: #4facfe;
box-shadow: 0 0 20px rgba(79, 172, 254, 0.6);
transform: translateY(-2px);
}
input:focus {
box-shadow: 0 0 20px rgba(79, 172, 254, 0.6), 0 0 0 3px rgba(79, 172, 254, 0.2);
}
.btn {
background: linear-gradient(90deg, #1a2a6c, #2a5298);
color: white;
padding: 16px 30px;
border: none;
border-radius: 12px;
cursor: pointer;
font-size: 1.1rem;
font-weight: 600;
width: 100%;
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
letter-spacing: 1px;
text-transform: uppercase;
position: relative;
overflow: hidden;
z-index: 1;
transform: translateZ(0);
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: 0.5s;
z-index: -1;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
background: linear-gradient(90deg, #2a5298, #1a2a6c);
}
.btn:hover::before {
left: 100%;
}
.btn:active {
transform: translateY(0);
}
.message {
padding: 15px;
margin: 20px 0;
border-radius: 12px;
text-align: center;
font-weight: 500;
animation: fadeIn 0.5s ease;
backdrop-filter: blur(5px);
border: 1px solid;
transform: translateZ(0);
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.success {
background: rgba(46, 204, 113, 0.2);
border: 1px solid rgba(46, 204, 113, 0.5);
color: #2ecc71;
}
.error {
background: rgba(231, 76, 60, 0.2);
border: 1px solid rgba(231, 76, 60, 0.5);
color: #e74c3c;
}
.format-note {
background: rgba(92, 107, 192, 0.2);
border: 1px solid rgba(92, 107, 192, 0.4);
border-radius: 12px;
padding: 20px;
margin: 25px 0;
font-size: 0.95rem;
transform: translateZ(0);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.format-note h3 {
color: #4facfe;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
}
.format-note h3 i {
margin-right: 10px;
}
.format-note p {
margin: 8px 0;
display: flex;
align-items: center;
gap: 10px;
}
.format-note p i {
margin-right: 10px;
color: #4facfe;
}
.footer {
text-align: center;
padding: 25px;
background: rgba(13, 19, 33, 0.9);
border-top: 1px solid rgba(92, 107, 192, 0.3);
font-size: 1.1rem;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transform: translateZ(0);
}
.footer i {
color: #e74c3c;
margin: 0 5px;
font-size: 1.3rem;
animation: heartbeat 1.5s infinite;
}
@keyframes heartbeat {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.footer p {
margin: 5px 0;
}
.settings-info {
background: rgba(13, 19, 33, 0.7);
border: 1px solid rgba(92, 107, 192, 0.3);
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
font-size: 0.9rem;
transform: translateZ(0);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.settings-info p {
margin: 8px 0;
display: flex;
justify-content: space-between;
}
.settings-info strong {
color: #4facfe;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(79, 172, 254, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(79, 172, 254, 0); }
100% { box-shadow: 0 0 0 0 rgba(79, 172, 254, 0); }
}
.floating {
animation: floating 3s ease-in-out infinite;
}
@keyframes floating {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
.glow {
text-shadow: 0 0 10px rgba(79, 172, 254, 0.7);
}
.input-group {
display: flex;
gap: 10px;
}
.input-group input {
flex: 1;
}
.input-group input:first-child {
border-radius: 12px 0 0 12px;
}
.input-group input:last-child {
border-radius: 0 12px 12px 0;
}
@media (max-width: 768px) {
.container {
margin: 10px;
border-radius: 15px;
}
.header h1 {
font-size: 2rem;
}
.content {
padding: 20px;
}
.input-group {
flex-direction: column;
}
.input-group input {
border-radius: 12px;
}
}
.animate-on-load {
animation: slideIn 0.8s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.form-group:nth-child(1) { animation-delay: 0.1s; }
.form-group:nth-child(2) { animation-delay: 0.2s; }
.form-group:nth-child(3) { animation-delay: 0.3s; }
.form-group:nth-child(4) { animation-delay: 0.4s; }
.form-group:nth-child(5) { animation-delay: 0.5s; }
.form-group:nth-child(6) { animation-delay: 0.6s; }
.form-group:nth-child(7) { animation-delay: 0.7s; }
.form-group {
animation: slideIn 0.5s ease-out forwards;
opacity: 0;
transform: translateY(20px);
}
.form-group.animate {
animation: slideIn 0.5s ease-out forwards;
}
.btn-container {
margin-top: 20px;
}
</style>
</head>
<body>
<div class="container animate-on-load">
<div class="header">
<h1 class="glow"><i class="fas fa-cog"></i> Video Settings</h1>
<p>Configure your video processing parameters</p>
</div>
<div class="content">
<?php if ($message): ?>
<div class="message <?php echo (strpos($message, 'Error') !== false) ? 'error' : 'success'; ?>">
<?php echo $message; ?>
</div>
<?php endif; ?>
<div class="settings-info">
<p><strong>Current Settings:</strong>
<span>Video: <?php echo htmlspecialchars($settings['video_format']); ?></span> |
<span>Audio: <?php echo htmlspecialchars($settings['audio_format']); ?></span>
</p>
</div>
<div class="format-note">
<h3><i class="fas fa-info-circle"></i> Format Restrictions</h3>
<p><i class="fas fa-video"></i> Video format is limited to: MPEG-2, H.264, H.265, VP9, and AV1</p>
<p><i class="fas fa-music"></i> Audio format is limited to: MP2, MP3, and AAC</p>
</div>
<form method="POST" action="">
<div class="form-group">
<label for="video_format"><i class="fas fa-film"></i> Video Format</label>
<select name="video_format" id="video_format">
<option value="mpeg2" <?php echo ($settings['video_format'] === 'mpeg2') ? 'selected' : ''; ?>>MPEG-2</option>
<option value="h264" <?php echo ($settings['video_format'] === 'h264') ? 'selected' : ''; ?>>H.264</option>
<option value="h265" <?php echo ($settings['video_format'] === 'h265') ? 'selected' : ''; ?>>H.265</option>
<option value="vp9" <?php echo ($settings['video_format'] === 'vp9') ? 'selected' : ''; ?>>VP9</option>
<option value="av1" <?php echo ($settings['video_format'] === 'av1') ? 'selected' : ''; ?>>AV1</option>
</select>
</div>
<div class="form-group">
<label for="audio_format"><i class="fas fa-music"></i> Audio Format</label>
<select name="audio_format" id="audio_format">
<option value="mp2" <?php echo ($settings['audio_format'] === 'mp2') ? 'selected' : ''; ?>>MP2</option>
<option value="mp3" <?php echo ($settings['audio_format'] === 'mp3') ? 'selected' : ''; ?>>MP3</option>
<option value="aac" <?php echo ($settings['audio_format'] === 'aac') ? 'selected' : ''; ?>>AAC</option>
</select>
</div>
<div class="form-group">
<label for="video_bitrate"><i class="fas fa-tachometer-alt"></i> Video Bitrate</label>
<input type="text" name="video_bitrate" id="video_bitrate" value="<?php echo htmlspecialchars($settings['video_bitrate']); ?>">
</div>
<div class="form-group">
<label for="audio_bitrate"><i class="fas fa-volume-up"></i> Audio Bitrate</label>
<input type="text" name="audio_bitrate" id="audio_bitrate" value="<?php echo htmlspecialchars($settings['audio_bitrate']); ?>">
</div>
<div class="form-group">
<label for="resolution"><i class="fas fa-expand"></i> Resolution</label>
<select name="resolution" id="resolution">
<option value="720x576" <?php echo ($settings['resolution'] === '720x576') ? 'selected' : ''; ?>>720x576 (PAL)</option>
<option value="720x480" <?php echo ($settings['resolution'] === '720x480') ? 'selected' : ''; ?>>720x480 (NTSC)</option>
<option value="1280x720" <?php echo ($settings['resolution'] === '1280x720') ? 'selected' : ''; ?>>1280x720 (HD 720p)</option>
<option value="1920x1080" <?php echo ($settings['resolution'] === '1920x1080') ? 'selected' : ''; ?>>1920x1080 (HD 1080p)</option>
<option value="2560x1440" <?php echo ($settings['resolution'] === '2560x1440') ? 'selected' : ''; ?>>2560x1440 (QHD 1440p)</option>
<option value="3840x2160" <?php echo ($settings['resolution'] === '3840x2160') ? 'selected' : ''; ?>>3840x2160 (4K UHD)</option>
<option value="4096x2160" <?php echo ($settings['resolution'] === '4096x2160') ? 'selected' : ''; ?>>4096x2160 (4K DCI)</option>
</select>
</div>
<div class="form-group">
<label for="frame_rate"><i class="fas fa-play-circle"></i> Frame Rate (fps)</label>
<input type="text" name="frame_rate" id="frame_rate" value="<?php echo htmlspecialchars($settings['frame_rate']); ?>">
</div>
<div class="form-group">
<label for="gop"><i class="fas fa-layer-group"></i> GOP (Group of Pictures)</label>
<input type="text" name="gop" id="gop" value="<?php echo htmlspecialchars($settings['gop']); ?>">
</div>
<div class="btn-container">
<button type="submit" class="btn pulse"><i class="fas fa-save"></i> Save Settings</button>
</div>
</form>
</div>
<div class="footer">
<p>Made with <i class="fas fa-heart"></i> from ShreeBhattJi</p>
</div>
</div>
</body>
</html>

9
settings/settings.json Normal file
View File

@ -0,0 +1,9 @@
{
"video_format": "mpeg2",
"audio_format": "mp3",
"video_bitrate": "1000k",
"audio_bitrate": "128k",
"resolution": "1280x720",
"frame_rate": "30",
"gop": "30"
}

50
setup.sh Executable file
View File

@ -0,0 +1,50 @@
apt updtae;
apt upgrade -y;
apt install php ffmpeg apache2 samba -y
mkdir -p /var/www/download /var/www/downloader /var/www/download/queue /var/www/download/ready
cp -r downloader/* /var/www/downloader
sudo smbpasswd -a shreebhattji
echo "foreverstreamingpartner" | sudo smbpasswd -s -a shreebhattji
sudo chmod 755 /var/www/download
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.backup
cat << EOF | sudo tee -a /etc/samba/smb.conf
[download]
path = /var/www/download
browseable = yes
writable = yes
guest ok = no
read only = no
valid users = shreebhattji
EOF
sudo systemctl restart smbd nmbd
sudo systemctl enable smbd nmbd
cat << EOF | sudo tee -a /etc/systemd/system/transcode.service
[Unit]
Description=Video Transcoding Service
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/
ExecStart=/usr/bin/python3 /var/www/transcode.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF

86
transcode.sh Normal file
View File

@ -0,0 +1,86 @@
#!/bin/bash
# Configuration
QUEUE_DIR="/var/www/download/queue"
READY_DIR="/var/www/download/ready"
SETTINGS_FILE="/path/to/settings/settings.json"
LOG_FILE="/var/log/convert.log"
# Create directories if they don't exist
mkdir -p "$QUEUE_DIR"
mkdir -p "$READY_DIR"
# Load settings from JSON file
load_settings() {
VIDEO_FORMAT=$(jq -r '.video_format' "$SETTINGS_FILE")
AUDIO_FORMAT=$(jq -r '.audio_format' "$SETTINGS_FILE")
VIDEO_BITRATE=$(jq -r '.video_bitrate' "$SETTINGS_FILE")
AUDIO_BITRATE=$(jq -r '.audio_bitrate' "$SETTINGS_FILE")
RESOLUTION=$(jq -r '.resolution' "$SETTINGS_FILE")
FRAME_RATE=$(jq -r '.frame_rate' "$SETTINGS_FILE")
GOP=$(jq -r '.gop' "$SETTINGS_FILE")
}
# Function to sanitize filename
sanitize_filename() {
local filename="$1"
# Remove special characters, keep only a-z, A-Z, 0-9, and .
local sanitized=$(echo "$filename" | sed 's/[^a-zA-Z0-9.]*//g')
# If filename starts with number, add prefix
if [[ "$sanitized" =~ ^[0-9] ]]; then
sanitized="dev_${sanitized}"
fi
# If filename is empty after sanitization, use timestamp
if [[ -z "$sanitized" ]]; then
sanitized="dev_$(date +%s)"
fi
echo "$sanitized"
}
# Main processing loop
while true; do
# Load settings at start of each loop
load_settings
# Find all files in queue directory
for file in "$QUEUE_DIR"/*; do
# Skip if no files found
[ -f "$file" ] || continue
# Get original filename
original_name=$(basename "$file")
# Sanitize filename
sanitized_name=$(sanitize_filename "$original_name")
# Add .ts extension
sanitized_name="${sanitized_name}.ts"
# Set output path
output_file="$READY_DIR/$sanitized_name"
# Convert file with ffmpeg using settings from JSON
if ffmpeg -i "$file" \
-c:v "$VIDEO_FORMAT" \
-b:v "$VIDEO_BITRATE" \
-c:a "$AUDIO_FORMAT" \
-b:a "$AUDIO_BITRATE" \
-s "$RESOLUTION" \
-r "$FRAME_RATE" \
-g "$GOP" \
"$output_file" 2>>"$LOG_FILE"; then
# If conversion successful, remove original file
rm "$file"
echo "$(date): Successfully converted $original_name to $sanitized_name" >> "$LOG_FILE"
else
# If conversion failed, log error
echo "$(date): Failed to convert $original_name" >> "$LOG_FILE"
fi
done
# Wait before checking for new files
sleep 5
done