add generic Option field to ffmpeg and libvlc cameras
parent
9fb794f4d0
commit
ceff5a98ea
|
@ -157,7 +157,7 @@ PREPARE stmt FROM @s;
|
|||
EXECUTE stmt;
|
||||
|
||||
--
|
||||
-- Add Monitor Options field; used for specifying Ffmpeg AVoptions like rtsp_transport tcp
|
||||
-- Add Monitor Options field; used for specifying Ffmpeg AVoptions like rtsp_transport http or libVLC options
|
||||
--
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
|
|
|
@ -23,9 +23,10 @@
|
|||
|
||||
#include "zm_ffmpeg_camera.h"
|
||||
|
||||
FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
|
||||
FfmpegCamera::FfmpegCamera( int p_id, const std::string &p_path, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
|
||||
Camera( p_id, FFMPEG_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
|
||||
mPath( p_path )
|
||||
mPath( p_path ),
|
||||
mOptions( p_options )
|
||||
{
|
||||
if ( capture )
|
||||
{
|
||||
|
@ -110,12 +111,23 @@ void FfmpegCamera::Terminate()
|
|||
int FfmpegCamera::PrimeCapture()
|
||||
{
|
||||
Info( "Priming capture from %s", mPath.c_str() );
|
||||
|
||||
|
||||
|
||||
// Open the input, not necessarily a file
|
||||
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 4, 0)
|
||||
if ( av_open_input_file( &mFormatContext, mPath.c_str(), NULL, 0, NULL ) !=0 )
|
||||
#else
|
||||
if ( avformat_open_input( &mFormatContext, mPath.c_str(), NULL, NULL ) !=0 )
|
||||
// Handle options
|
||||
AVDictionary *opts = 0;
|
||||
StringVector opVect = split(Options(), ",");
|
||||
for (int i=0; i<opVect.size(), i++)
|
||||
{
|
||||
StringVector parts = split(opVect[i],"=");
|
||||
if (parts.size() > 1)
|
||||
av_dict_set(&opts, trimSpaces(parts[0]), trimSpaces(parts[1]));
|
||||
}
|
||||
if ( avformat_open_input( &mFormatContext, mPath.c_str(), NULL, &opts ) !=0 )
|
||||
#endif
|
||||
Fatal( "Unable to open input %s due to: %s", mPath.c_str(), strerror(errno) );
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ class FfmpegCamera : public Camera
|
|||
{
|
||||
protected:
|
||||
std::string mPath;
|
||||
std::string mOptions;
|
||||
|
||||
int frameCount;
|
||||
|
||||
|
@ -52,10 +53,11 @@ protected:
|
|||
#endif
|
||||
|
||||
public:
|
||||
FfmpegCamera( int p_id, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
|
||||
FfmpegCamera( int p_id, const std::string &path, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
|
||||
~FfmpegCamera();
|
||||
|
||||
const std::string &Path() const { return( mPath ); }
|
||||
const std::string &Options() const { return( mOptions ); }
|
||||
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
|
|
|
@ -61,9 +61,10 @@ void LibvlcUnlockBuffer(void* opaque, void* picture, void *const *planes)
|
|||
}
|
||||
}
|
||||
|
||||
LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
|
||||
LibvlcCamera::LibvlcCamera( int p_id, const std::string &p_path, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
|
||||
Camera( p_id, LIBVLC_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture ),
|
||||
mPath( p_path )
|
||||
mPath( p_path ),
|
||||
mOptions( p_options )
|
||||
{
|
||||
mLibvlcInstance = NULL;
|
||||
mLibvlcMedia = NULL;
|
||||
|
@ -115,6 +116,10 @@ LibvlcCamera::~LibvlcCamera()
|
|||
libvlc_release(mLibvlcInstance);
|
||||
mLibvlcInstance = NULL;
|
||||
}
|
||||
if (mOptArgV != NULL)
|
||||
{
|
||||
delete[] mOptArgV;
|
||||
}
|
||||
}
|
||||
|
||||
void LibvlcCamera::Initialise()
|
||||
|
@ -137,8 +142,17 @@ void LibvlcCamera::Terminate()
|
|||
int LibvlcCamera::PrimeCapture()
|
||||
{
|
||||
Info("Priming capture from %s", mPath.c_str());
|
||||
|
||||
mLibvlcInstance = libvlc_new (0, NULL);
|
||||
|
||||
StringVector opVect = split(Options(), ",");
|
||||
|
||||
if (opVect.size() > 0)
|
||||
{
|
||||
mOptArgV = new char*[opVect.size()];cmd
|
||||
for (int i=0; i< opVect.size(); i++)
|
||||
mOptArgV[i] = opVect[i].c_str();
|
||||
}
|
||||
|
||||
mLibvlcInstance = libvlc_new (opVect.size(), optArgV);
|
||||
if(mLibvlcInstance == NULL)
|
||||
Fatal("Unable to create libvlc instance due to: %s", libvlc_errmsg());
|
||||
|
||||
|
|
|
@ -45,7 +45,8 @@ class LibvlcCamera : public Camera
|
|||
{
|
||||
protected:
|
||||
std::string mPath;
|
||||
|
||||
std::string mOptions;
|
||||
unisgned char **mOptArgV;
|
||||
LibvlcPrivateData mLibvlcData;
|
||||
std::string mTargetChroma;
|
||||
uint8_t mBpp;
|
||||
|
@ -55,10 +56,11 @@ protected:
|
|||
libvlc_media_player_t *mLibvlcMediaPlayer;
|
||||
|
||||
public:
|
||||
LibvlcCamera( int p_id, const std::string &path, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
|
||||
LibvlcCamera( int p_id, const std::string &path, const std::string &p_options, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
|
||||
~LibvlcCamera();
|
||||
|
||||
const std::string &Path() const { return( mPath ); }
|
||||
const std::string &Options() const { return( mOptions ); }
|
||||
|
||||
void Initialise();
|
||||
void Terminate();
|
||||
|
|
|
@ -56,21 +56,6 @@
|
|||
#endif // ZM_MEM_MAPPED
|
||||
|
||||
//=============================================================================
|
||||
std::string trimSpaces(std::string str)
|
||||
{
|
||||
// Trim Both leading and trailing spaces
|
||||
size_t startpos = str.find_first_not_of(" \t"); // Find the first character position after excluding leading blank spaces
|
||||
size_t endpos = str.find_last_not_of(" \t"); // Find the first character position from reverse af
|
||||
|
||||
// if all spaces or empty return an empty string
|
||||
if(( std::string::npos == startpos ) || ( std::string::npos == endpos))
|
||||
{
|
||||
return std::string("");
|
||||
}
|
||||
else
|
||||
return str.substr( startpos, endpos-startpos+1 );
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string &s, char delim) {
|
||||
std::vector<std::string> elems;
|
||||
std::stringstream ss(s);
|
||||
|
@ -2306,11 +2291,11 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
|
|||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
if ( !file[0] )
|
||||
{
|
||||
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg'", sizeof(sql) );
|
||||
strncpy( sql, "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg'", sizeof(sql) );
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg' and Path = '%s'", file );
|
||||
snprintf( sql, sizeof(sql), "select Id, Name, Function+0, Enabled, LinkedMonitors, Path, Options, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion from Monitors where Function != 'None' and Type = 'Ffmpeg' and Path = '%s'", file );
|
||||
}
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
|
@ -2339,6 +2324,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
|
|||
const char *linked_monitors = dbrow[col]; col++;
|
||||
|
||||
const char *path = dbrow[col]; col++;
|
||||
const char *options = dbrow[col]; col++;
|
||||
|
||||
int width = atoi(dbrow[col]); col++;
|
||||
int height = atoi(dbrow[col]); col++;
|
||||
|
@ -2379,6 +2365,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
|
|||
Camera *camera = new FfmpegCamera(
|
||||
id,
|
||||
path, // File
|
||||
options,
|
||||
cam_width,
|
||||
cam_height,
|
||||
colours,
|
||||
|
@ -2441,7 +2428,7 @@ int Monitor::LoadFfmpegMonitors( const char *file, Monitor **&monitors, Purpose
|
|||
Monitor *Monitor::Load( int id, bool load_zones, Purpose purpose )
|
||||
{
|
||||
static char sql[ZM_SQL_MED_BUFSIZ];
|
||||
snprintf( sql, sizeof(sql), "select Id, Name, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, Protocol, Method, Host, Port, Path, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = %d", id );
|
||||
snprintf( sql, sizeof(sql), "select Id, Name, Type, Function+0, Enabled, LinkedMonitors, Device, Channel, Format, Protocol, Method, Host, Port, Path, Options, User, Pass, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, Brightness, Contrast, Hue, Colour, EventPrefix, LabelFormat, LabelX, LabelY, ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = %d", id );
|
||||
if ( mysql_query( &dbconn, sql ) )
|
||||
{
|
||||
Error( "Can't run query: %s", mysql_error( &dbconn ) );
|
||||
|
@ -2477,6 +2464,7 @@ Monitor *Monitor::Load( int id, bool load_zones, Purpose purpose )
|
|||
std::string host = dbrow[col]; col++;
|
||||
std::string port = dbrow[col]; col++;
|
||||
std::string path = dbrow[col]; col++;
|
||||
std::string options = dbrow[col]; col++;
|
||||
std::string user = dbrow[col]; col++;
|
||||
std::string pass = dbrow[col]; col++;
|
||||
|
||||
|
@ -2617,6 +2605,7 @@ Monitor *Monitor::Load( int id, bool load_zones, Purpose purpose )
|
|||
camera = new FfmpegCamera(
|
||||
id,
|
||||
path.c_str(),
|
||||
options,
|
||||
cam_width,
|
||||
cam_height,
|
||||
colours,
|
||||
|
@ -2636,6 +2625,7 @@ Monitor *Monitor::Load( int id, bool load_zones, Purpose purpose )
|
|||
camera = new LibvlcCamera(
|
||||
id,
|
||||
path.c_str(),
|
||||
options,
|
||||
cam_width,
|
||||
cam_height,
|
||||
colours,
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "zm_zone.h"
|
||||
#include "zm_event.h"
|
||||
#include "zm_camera.h"
|
||||
#include "zm_utils.h"
|
||||
|
||||
#include "zm_image_analyser.h"
|
||||
|
||||
|
|
|
@ -27,6 +27,36 @@
|
|||
|
||||
unsigned int sseversion = 0;
|
||||
|
||||
std::string trimSet(std::string str, std::string trimset) {
|
||||
// Trim Both leading and trailing sets
|
||||
size_t startpos = str.find_first_not_of(trimset); // Find the first character position after excluding leading blank spaces
|
||||
size_t endpos = str.find_last_not_of(trimset); // Find the first character position from reverse af
|
||||
|
||||
// if all spaces or empty return an empty string
|
||||
if(( std::string::npos == startpos ) || ( std::string::npos == endpos))
|
||||
{
|
||||
return std::string("");
|
||||
}
|
||||
else
|
||||
return str.substr( startpos, endpos-startpos+1 );
|
||||
}
|
||||
|
||||
std::string trimSpaces(std::string str)
|
||||
{
|
||||
return trimSet(str, " \t");
|
||||
}
|
||||
|
||||
std::string replaceAll(std::string str, std::string from, std::string to) {
|
||||
if(from.empty())
|
||||
return str;
|
||||
size_t start_pos = 0;
|
||||
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
const std::string stringtf( const char *format, ... )
|
||||
{
|
||||
va_list ap;
|
||||
|
|
|
@ -27,6 +27,10 @@
|
|||
|
||||
typedef std::vector<std::string> StringVector;
|
||||
|
||||
std::string trimSpaces(std::string str);
|
||||
std::string trimSet(std::string str, std::string trimset);
|
||||
std::string replaceAll(std::string str, std::string from, std::string to);
|
||||
|
||||
const std::string stringtf( const char *format, ... );
|
||||
const std::string stringtf( const std::string &format, ... );
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ else
|
|||
'Method' => "",
|
||||
'Host' => "",
|
||||
'Path' => "",
|
||||
'Options' => "",
|
||||
'Port' => "80",
|
||||
'User' => "",
|
||||
'Pass' => "",
|
||||
|
@ -506,6 +507,12 @@ if ( $tab != 'source' || ($newMonitor['Type'] != 'Local' && $newMonitor['Type']
|
|||
<input type="hidden" name="newMonitor[Method]" value="<?= validHtmlStr($newMonitor['Method']) ?>"/>
|
||||
<?php
|
||||
}
|
||||
if ( $tab != 'source' || ($newMonitor['Type'] != 'Ffmpeg' && $newMonitor['Type'] != 'Libvlc' ))
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="newMonitor[Options]" value="<?= validHtmlStr($newMonitor['Options']) ?>"/>
|
||||
<?php
|
||||
}
|
||||
if ( $tab != 'source' || ($newMonitor['Type'] != 'Remote' && $newMonitor['Type'] != 'File' && $newMonitor['Type'] != 'Ffmpeg' && $newMonitor['Type'] != 'Libvlc' && $newMonitor['Type'] != 'cURL') )
|
||||
{
|
||||
?>
|
||||
|
@ -722,7 +729,7 @@ switch ( $tab )
|
|||
<tr><td><?= $SLANG['RemoteHostPath'] ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
<?php
|
||||
}
|
||||
elseif ( $newMonitor['Type'] == "File" || $newMonitor['Type'] == "Ffmpeg" || $newMonitor['Type'] == "Libvlc" )
|
||||
elseif ( $newMonitor['Type'] == "File" )
|
||||
{
|
||||
?>
|
||||
<tr><td><?= $SLANG['SourcePath'] ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
|
@ -734,6 +741,13 @@ switch ( $tab )
|
|||
<tr><td><?= "URL" ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
<tr><td><?= "Username" ?></td><td><input type="text" name="newMonitor[User]" value="<?= validHtmlStr($newMonitor['User']) ?>" size="12"/></td></tr>
|
||||
<tr><td><?= "Password" ?></td><td><input type="text" name="newMonitor[Pass]" value="<?= validHtmlStr($newMonitor['Pass']) ?>" size="12"/></td></tr>
|
||||
<?php
|
||||
}
|
||||
elseif ( $newMonitor['Type'] == "Ffmpeg" || $newMonitor['Type'] == "Libvlc")
|
||||
{
|
||||
?>
|
||||
<tr><td><?= $SLANG['SourcePath'] ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
<tr><td><?= "Options" ?></td><td><input type="text" name="newMonitor[Options]" value="<?= validHtmlStr($newMonitor['Options']) ?>" size="36"/></td></tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
|
|
@ -63,6 +63,7 @@ else
|
|||
'Method' => "",
|
||||
'Host' => "",
|
||||
'Path' => "",
|
||||
'Options' => "",
|
||||
'Port' => "80",
|
||||
'User' => "",
|
||||
'Pass' => "",
|
||||
|
@ -507,6 +508,12 @@ if ( $tab != 'source' || ($newMonitor['Type'] != 'Local' && $newMonitor['Type']
|
|||
<input type="hidden" name="newMonitor[Method]" value="<?= validHtmlStr($newMonitor['Method']) ?>"/>
|
||||
<?php
|
||||
}
|
||||
if ( $tab != 'source' || ($newMonitor['Type'] != 'Ffmpeg' && $newMonitor['Type'] != 'Libvlc' ))
|
||||
{
|
||||
?>
|
||||
<input type="hidden" name="newMonitor[Options]" value="<?= validHtmlStr($newMonitor['Options']) ?>"/>
|
||||
<?php
|
||||
}
|
||||
if ( $tab != 'source' || ($newMonitor['Type'] != 'Remote' && $newMonitor['Type'] != 'File' && $newMonitor['Type'] != 'Ffmpeg' && $newMonitor['Type'] != 'Libvlc' && $newMonitor['Type'] != 'cURL') )
|
||||
{
|
||||
?>
|
||||
|
@ -723,7 +730,7 @@ switch ( $tab )
|
|||
<tr><td><?= $SLANG['RemoteHostPath'] ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
<?php
|
||||
}
|
||||
elseif ( $newMonitor['Type'] == "File" || $newMonitor['Type'] == "Ffmpeg" || $newMonitor['Type'] == "Libvlc" )
|
||||
elseif ( $newMonitor['Type'] == "File" )
|
||||
{
|
||||
?>
|
||||
<tr><td><?= $SLANG['SourcePath'] ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
|
@ -735,6 +742,13 @@ switch ( $tab )
|
|||
<tr><td><?= "URL" ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
<tr><td><?= "Username" ?></td><td><input type="text" name="newMonitor[User]" value="<?= validHtmlStr($newMonitor['User']) ?>" size="12"/></td></tr>
|
||||
<tr><td><?= "Password" ?></td><td><input type="text" name="newMonitor[Pass]" value="<?= validHtmlStr($newMonitor['Pass']) ?>" size="12"/></td></tr>
|
||||
<?php
|
||||
}
|
||||
elseif ( $newMonitor['Type'] == "Ffmpeg" || $newMonitor['Type'] == "Libvlc")
|
||||
{
|
||||
?>
|
||||
<tr><td><?= $SLANG['SourcePath'] ?></td><td><input type="text" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>" size="36"/></td></tr>
|
||||
<tr><td><?= "Options" ?></td><td><input type="text" name="newMonitor[Options]" value="<?= validHtmlStr($newMonitor['Options']) ?>" size="36"/></td></tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
|
|
Loading…
Reference in New Issue