megui job creator added
--HG-- branch : sandbox
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
<path>/sandbox/src</path>
|
<path>/sandbox/src</path>
|
||||||
<path>/sandbox/gui</path>
|
<path>/sandbox/gui</path>
|
||||||
<path>/sandbox/mpl</path>
|
<path>/sandbox/mpl</path>
|
||||||
|
<path>/sandbox/megui</path>
|
||||||
</pydev_pathproperty>
|
</pydev_pathproperty>
|
||||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.5</pydev_property>
|
||||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||||
|
|||||||
184
megui/create_jobs.py
Normal file
184
megui/create_jobs.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Copyright (c) 2009 Andreas Balogh
|
||||||
|
# See LICENSE for details.
|
||||||
|
|
||||||
|
""" create MeGUI jobs for Panasonic MPG files """
|
||||||
|
|
||||||
|
# system imports
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import getopt
|
||||||
|
import re
|
||||||
|
import xml.dom.minidom as xdm
|
||||||
|
import xml.dom
|
||||||
|
|
||||||
|
# local imports
|
||||||
|
|
||||||
|
# constants
|
||||||
|
|
||||||
|
# globals
|
||||||
|
|
||||||
|
LOG = logging.getLogger()
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format="%(asctime)s %(levelname).3s %(process)d:%(thread)d %(message)s",
|
||||||
|
datefmt="%H:%M:%S")
|
||||||
|
|
||||||
|
# definitions
|
||||||
|
|
||||||
|
class Usage(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def main(argv = [__name__]):
|
||||||
|
try:
|
||||||
|
# check for parameters
|
||||||
|
LOG.info("starting '%s %s'", argv[0], " ".join(argv[1:]))
|
||||||
|
script_name = os.path.basename(argv[0])
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(argv[1:], "ht:j:z:", \
|
||||||
|
["help", "template", "job_dir", "temp_dir"])
|
||||||
|
except getopt.error, err:
|
||||||
|
raise Usage(err)
|
||||||
|
LOG.debug("opts: %s, args: %s", opts, args)
|
||||||
|
template = "job_divx_template.xml"
|
||||||
|
job_dir = "C:\\Program Files\\megui\\jobs"
|
||||||
|
temp_dir = "e:\\video"
|
||||||
|
for o, a in opts:
|
||||||
|
if o in ("-h", "--help"):
|
||||||
|
usage(script_name)
|
||||||
|
return 0
|
||||||
|
elif o in ("-t", "--template"):
|
||||||
|
template = a
|
||||||
|
elif o in ("-j", "--job_dir"):
|
||||||
|
job_dir = a
|
||||||
|
elif o in ("-z", "--temp_dir"):
|
||||||
|
temp_dir = a
|
||||||
|
src_dir = "D:\\Documents\\Raw\\Panasonic SDR-S7\\Video"
|
||||||
|
dest_dir = "D:\\Documents\\My Videos\\Balogh"
|
||||||
|
if len(args) == 2:
|
||||||
|
src_dir = args[0]
|
||||||
|
dest_dir = args[1]
|
||||||
|
elif len(args) == 1:
|
||||||
|
src_dir = args[0]
|
||||||
|
elif len(args) > 2:
|
||||||
|
raise Usage("too many arguments")
|
||||||
|
# call method with appropriate arguments
|
||||||
|
if src_dir and not os.path.exists(src_dir):
|
||||||
|
raise Error("Source directory not found [%s], aborting" % (src_dir, ))
|
||||||
|
if dest_dir and not os.path.exists(dest_dir):
|
||||||
|
LOG.warn("Destination directory not found [%s]", dest_dir)
|
||||||
|
LOG.info("Creating destination directory [%s]", dest_dir)
|
||||||
|
os.makedirs(dest_dir)
|
||||||
|
cli(src_dir, dest_dir, template, job_dir, temp_dir)
|
||||||
|
LOG.info("Done.")
|
||||||
|
return 0
|
||||||
|
except Error, err:
|
||||||
|
LOG.error(err)
|
||||||
|
return 1
|
||||||
|
except Usage, err:
|
||||||
|
LOG.error(err)
|
||||||
|
LOG.info("for usage use -h or --help")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def cli(src_dir, dest_dir, template, job_dir, tmp_dir):
|
||||||
|
""" command line interface """
|
||||||
|
if not os.path.exists(dest_dir):
|
||||||
|
LOG.error("Destination directory not found [%s]", dest_dir)
|
||||||
|
raise Error()
|
||||||
|
for mpg_fn in os.listdir(src_dir):
|
||||||
|
opts = { }
|
||||||
|
root, ext = os.path.splitext(mpg_fn)
|
||||||
|
# skip files which are not mpg video
|
||||||
|
if ext.lower() != ".mpg":
|
||||||
|
continue
|
||||||
|
# determine file paths
|
||||||
|
mpg_fp = os.path.join(src_dir, mpg_fn)
|
||||||
|
avi_fp = os.path.join(dest_dir, root + ".avi")
|
||||||
|
d2v_fp = os.path.join(tmp_dir, root + ".d2v")
|
||||||
|
opts["mpg_input"] = mpg_fp
|
||||||
|
opts["final_output"] = avi_fp
|
||||||
|
opts["d2v_output"] = d2v_fp
|
||||||
|
# if avi_fn older than mpg or doesn't exist
|
||||||
|
# then create job xml
|
||||||
|
if not os.path.exists(avi_fp):
|
||||||
|
create_megui_job(template, job_dir, opts)
|
||||||
|
|
||||||
|
def append_megui_joblist(jobname):
|
||||||
|
joblist = "c:\\program files\\megui\\joblists.xml"
|
||||||
|
# load the xml
|
||||||
|
inf = open(joblist,"r")
|
||||||
|
dom = xdm.parse(inf)
|
||||||
|
inf.close()
|
||||||
|
# add <string>job220</string> to mainJobList
|
||||||
|
JobListSerializer = dom.getElementsByTagName("JobListSerializer")[0]
|
||||||
|
mainJobList = JobListSerializer.getElementsByTagName("mainJobList")[0]
|
||||||
|
string = dom.createElement("string")
|
||||||
|
text = dom.createTextNode(jobname)
|
||||||
|
string.appendChild(text)
|
||||||
|
mainJobList.appendChild(string)
|
||||||
|
newline = dom.createTextNode("\n ")
|
||||||
|
mainJobList.appendChild(newline)
|
||||||
|
# save xml
|
||||||
|
outf = open(joblist,"w")
|
||||||
|
dom.writexml(outf)
|
||||||
|
outf.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_megui_job(template, job_dir, opts):
|
||||||
|
# replace in template: mpg_input, d2v_output, final_output, jobnum
|
||||||
|
jobnum = get_next_jobnumber(job_dir)
|
||||||
|
LOG.info("creating job %i...", jobnum)
|
||||||
|
jobfn = "job%i.xml" % (jobnum, )
|
||||||
|
jobname = "job%i" % (jobnum, )
|
||||||
|
opts["jobname"] = jobname
|
||||||
|
tplf = open(template, "r")
|
||||||
|
jobf = open(os.path.join(job_dir, jobfn), "w")
|
||||||
|
try:
|
||||||
|
for line in tplf:
|
||||||
|
# line = raw_line.rstrip("\n\r")
|
||||||
|
for k, v in opts.items():
|
||||||
|
kd = "".join(("$", k, "$"))
|
||||||
|
line = line.replace(kd, v)
|
||||||
|
jobf.write(line)
|
||||||
|
finally:
|
||||||
|
jobf.close()
|
||||||
|
tplf.close()
|
||||||
|
append_megui_joblist(jobname)
|
||||||
|
|
||||||
|
JOB_REO = re.compile("job(\d+)\.xml")
|
||||||
|
|
||||||
|
def get_next_jobnumber(job_dir):
|
||||||
|
max_jn = 0
|
||||||
|
for job_fn in os.listdir(job_dir):
|
||||||
|
job_mo = JOB_REO.match(job_fn)
|
||||||
|
if job_mo:
|
||||||
|
jn = int(job_mo.group(1))
|
||||||
|
if jn > max_jn:
|
||||||
|
max_jn = jn
|
||||||
|
return max_jn + 1
|
||||||
|
|
||||||
|
|
||||||
|
def usage(script_name):
|
||||||
|
print
|
||||||
|
print "usage: %s [-t template] [-j job_dir] [-z temp_dir] [src_dir [dst_dir]]" % (script_name,)
|
||||||
|
print """
|
||||||
|
src_dir source directory of original MPG files
|
||||||
|
dest_dir destination directory for compressed video
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-t, --template job template file
|
||||||
|
-j, --job_dir megui job directory
|
||||||
|
-z, --temp_dir working directory for megui
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main(sys.argv))
|
||||||
|
|
||||||
154
megui/job_divx_template.xml
Normal file
154
megui/job_divx_template.xml
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<TaggedJob xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||||
|
<EncodingSpeed />
|
||||||
|
<Job xsi:type="IndexJob">
|
||||||
|
<Input>$mpg_input$</Input>
|
||||||
|
<Output>$d2v_output$</Output>
|
||||||
|
<FilesToDelete />
|
||||||
|
<LoadSources>false</LoadSources>
|
||||||
|
<DemuxVideo>false</DemuxVideo>
|
||||||
|
<DemuxMode>1</DemuxMode>
|
||||||
|
<AudioTracks>
|
||||||
|
<AudioTrackInfo>
|
||||||
|
<TrackInfo>
|
||||||
|
<Language />
|
||||||
|
</TrackInfo>
|
||||||
|
<Language />
|
||||||
|
<TrackIDx>c0</TrackIDx>
|
||||||
|
<TrackID>192</TrackID>
|
||||||
|
<ContainerType>MPEG-PS</ContainerType>
|
||||||
|
<Index>0</Index>
|
||||||
|
<Type>MP2</Type>
|
||||||
|
<NbChannels>2 channels</NbChannels>
|
||||||
|
<SamplingRate>48.0 KHz</SamplingRate>
|
||||||
|
</AudioTrackInfo>
|
||||||
|
</AudioTracks>
|
||||||
|
<PostprocessingProperties>
|
||||||
|
<AudioJobs>
|
||||||
|
<AudioJob>
|
||||||
|
<Input>::192::</Input>
|
||||||
|
<FilesToDelete />
|
||||||
|
<Settings xsi:type="MP3Settings">
|
||||||
|
<delay>0</delay>
|
||||||
|
<delayEnabled>false</delayEnabled>
|
||||||
|
<ForceDecodingViaDirectShow>false</ForceDecodingViaDirectShow>
|
||||||
|
<DownmixMode>StereoDownmix</DownmixMode>
|
||||||
|
<BitrateMode>ABR</BitrateMode>
|
||||||
|
<Bitrate>128</Bitrate>
|
||||||
|
<AutoGain>true</AutoGain>
|
||||||
|
<SampleRateType>0</SampleRateType>
|
||||||
|
<ApplyDRC>false</ApplyDRC>
|
||||||
|
<Normalize>100</Normalize>
|
||||||
|
<Quality>50</Quality>
|
||||||
|
</Settings>
|
||||||
|
<Delay>0</Delay>
|
||||||
|
<SizeBytes>0</SizeBytes>
|
||||||
|
<BitrateMode>CBR</BitrateMode>
|
||||||
|
</AudioJob>
|
||||||
|
</AudioJobs>
|
||||||
|
<DirectMuxAudio />
|
||||||
|
<AutoDeinterlace>false</AutoDeinterlace>
|
||||||
|
<AutoDeriveAR>false</AutoDeriveAR>
|
||||||
|
<SignalAR>true</SignalAR>
|
||||||
|
<AutoCrop>false</AutoCrop>
|
||||||
|
<KeepInputResolution>true</KeepInputResolution>
|
||||||
|
<PrerenderJob>false</PrerenderJob>
|
||||||
|
<HorizontalOutputResolution>640</HorizontalOutputResolution>
|
||||||
|
<ContainerTypeString>AVI</ContainerTypeString>
|
||||||
|
<OutputSize xsi:nil="true" />
|
||||||
|
<Splitting xsi:nil="true" />
|
||||||
|
<DAR xsi:nil="true" />
|
||||||
|
<AvsSettings>
|
||||||
|
<Mod16Method>none</Mod16Method>
|
||||||
|
<Resize>false</Resize>
|
||||||
|
<Template><input>
|
||||||
|
Load_Stdcall_Plugin("C:\Program Files\megui\tools\yadif\yadif.dll")
|
||||||
|
Yadif(order=1)
|
||||||
|
<crop>
|
||||||
|
<resize>
|
||||||
|
<denoise>
|
||||||
|
</Template>
|
||||||
|
<ResizeMethod>Lanczos</ResizeMethod>
|
||||||
|
<DenoiseMethod>MinimalNoise</DenoiseMethod>
|
||||||
|
<Deinterlace>false</Deinterlace>
|
||||||
|
<Denoise>false</Denoise>
|
||||||
|
<IVTC>false</IVTC>
|
||||||
|
<MPEG2Deblock>false</MPEG2Deblock>
|
||||||
|
<ColourCorrect>true</ColourCorrect>
|
||||||
|
<DSS2>false</DSS2>
|
||||||
|
</AvsSettings>
|
||||||
|
<VideoSettings xsi:type="xvidSettings">
|
||||||
|
<EncodingMode>4</EncodingMode>
|
||||||
|
<BitrateQuantizer>1200</BitrateQuantizer>
|
||||||
|
<KeyframeInterval>250</KeyframeInterval>
|
||||||
|
<NbBframes>2</NbBframes>
|
||||||
|
<MinQuantizer>1</MinQuantizer>
|
||||||
|
<MaxQuantizer>31</MaxQuantizer>
|
||||||
|
<Turbo>true</Turbo>
|
||||||
|
<V4MV>false</V4MV>
|
||||||
|
<QPel>true</QPel>
|
||||||
|
<Trellis>true</Trellis>
|
||||||
|
<CreditsQuantizer>15</CreditsQuantizer>
|
||||||
|
<FourCCs>
|
||||||
|
<string>XVID</string>
|
||||||
|
<string>DIVX</string>
|
||||||
|
<string>DX50</string>
|
||||||
|
<string>MP4V</string>
|
||||||
|
</FourCCs>
|
||||||
|
<Logfile>.stats</Logfile>
|
||||||
|
<VideoName />
|
||||||
|
<CustomEncoderOptions />
|
||||||
|
<FourCC>0</FourCC>
|
||||||
|
<MaxNumberOfPasses>2</MaxNumberOfPasses>
|
||||||
|
<NbThreads>1</NbThreads>
|
||||||
|
<Quantizer>1200</Quantizer>
|
||||||
|
<MotionSearchPrecision>6</MotionSearchPrecision>
|
||||||
|
<VHQMode>4</VHQMode>
|
||||||
|
<MinPQuant>1</MinPQuant>
|
||||||
|
<MaxPQuant>31</MaxPQuant>
|
||||||
|
<MinBQuant>1</MinBQuant>
|
||||||
|
<MaxBQuant>31</MaxBQuant>
|
||||||
|
<BQuantRatio>162</BQuantRatio>
|
||||||
|
<BQuantOffset>0</BQuantOffset>
|
||||||
|
<KeyFrameBoost>100</KeyFrameBoost>
|
||||||
|
<KeyframeThreshold>1</KeyframeThreshold>
|
||||||
|
<KeyframeReduction>20</KeyframeReduction>
|
||||||
|
<OverflowControlStrength>5</OverflowControlStrength>
|
||||||
|
<MaxOverflowImprovement>5</MaxOverflowImprovement>
|
||||||
|
<MaxOverflowDegradation>5</MaxOverflowDegradation>
|
||||||
|
<HighBitrateDegradation>30</HighBitrateDegradation>
|
||||||
|
<LowBitrateImprovement>15</LowBitrateImprovement>
|
||||||
|
<ReactionDelayFactor>16</ReactionDelayFactor>
|
||||||
|
<AveragingPeriod>100</AveragingPeriod>
|
||||||
|
<FrameDropRatio>0</FrameDropRatio>
|
||||||
|
<RateControlBuffer>0</RateControlBuffer>
|
||||||
|
<XvidProfile>0</XvidProfile>
|
||||||
|
<VbvPeakRate>0</VbvPeakRate>
|
||||||
|
<VbvMaxRate>0</VbvMaxRate>
|
||||||
|
<VbvBuffer>0</VbvBuffer>
|
||||||
|
<PackedBitstream>false</PackedBitstream>
|
||||||
|
<GMC>false</GMC>
|
||||||
|
<ChromaMotion>true</ChromaMotion>
|
||||||
|
<ClosedGOP>true</ClosedGOP>
|
||||||
|
<VHQForBframes>true</VHQForBframes>
|
||||||
|
<AdaptiveQuant>true</AdaptiveQuant>
|
||||||
|
<Interlaced>false</Interlaced>
|
||||||
|
<BottomFieldFirst>true</BottomFieldFirst>
|
||||||
|
<LumiMasking>false</LumiMasking>
|
||||||
|
<BframeThreshold>0</BframeThreshold>
|
||||||
|
<QuantizerMatrix>H.263</QuantizerMatrix>
|
||||||
|
</VideoSettings>
|
||||||
|
<CustomAR>1</CustomAR>
|
||||||
|
<ChapterFile />
|
||||||
|
<FinalOutput>$final_output$</FinalOutput>
|
||||||
|
<DeviceOutputType />
|
||||||
|
<UseChaptersMarks>false</UseChaptersMarks>
|
||||||
|
</PostprocessingProperties>
|
||||||
|
</Job>
|
||||||
|
<RequiredJobNames />
|
||||||
|
<EnabledJobNames />
|
||||||
|
<Name>$jobname$</Name>
|
||||||
|
<Status>WAITING</Status>
|
||||||
|
<Start>0001-01-01T00:00:00</Start>
|
||||||
|
<End>0001-01-01T00:00:00</End>
|
||||||
|
</TaggedJob>
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
# Copyright (c) 2009 Andreas Balogh
|
|
||||||
# See LICENSE for details.
|
|
||||||
|
|
||||||
""" create MeGUI jobs for Panasonic MPG files """
|
|
||||||
|
|
||||||
# system imports
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import getopt
|
|
||||||
|
|
||||||
# local imports
|
|
||||||
|
|
||||||
# constants
|
|
||||||
|
|
||||||
# globals
|
|
||||||
|
|
||||||
LOG = logging.getLogger()
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.DEBUG,
|
|
||||||
format="%(asctime)s %(levelname).3s %(process)d:%(thread)d %(message)s",
|
|
||||||
datefmt="%H:%M:%S")
|
|
||||||
|
|
||||||
# definitions
|
|
||||||
|
|
||||||
class Usage(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def main(argv = [__name__]):
|
|
||||||
try:
|
|
||||||
# check for parameters
|
|
||||||
LOG.debug("starting '%s %s'", argv[0], " ".join(argv[1:]))
|
|
||||||
script_name = os.path.basename(argv[0])
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(argv[1:], "hfgp", \
|
|
||||||
["help", "force", "gui", "preview"])
|
|
||||||
except getopt.error, err:
|
|
||||||
raise Usage(err)
|
|
||||||
LOG.debug("opts: %s, args: %s", opts, args)
|
|
||||||
o_overwrite = False
|
|
||||||
o_gui = False
|
|
||||||
o_preview = False
|
|
||||||
for o, a in opts:
|
|
||||||
if o in ("-h", "--help"):
|
|
||||||
usage(script_name)
|
|
||||||
return 0
|
|
||||||
elif o in ("-f", "--force"):
|
|
||||||
o_overwrite = True
|
|
||||||
elif o in ("-p", "--preview"):
|
|
||||||
o_preview = True
|
|
||||||
elif o in ("-g", "--gui"):
|
|
||||||
o_gui = True
|
|
||||||
if len(args) == 2:
|
|
||||||
src_dir = args[0]
|
|
||||||
dest_dir = args[1]
|
|
||||||
elif len(args) == 1 :
|
|
||||||
src_dir = args[0]
|
|
||||||
dest_dir = args[0]
|
|
||||||
elif len(args) == 0 :
|
|
||||||
src_dir = None
|
|
||||||
dest_dir = None
|
|
||||||
o_gui = True
|
|
||||||
else:
|
|
||||||
raise Usage("more than two arguments provided")
|
|
||||||
# call method with appropriate arguments
|
|
||||||
if src_dir and not os.path.exists(src_dir):
|
|
||||||
raise Error("Source directory not found [%s], aborting" % (src_dir, ))
|
|
||||||
if dest_dir and not os.path.exists(dest_dir):
|
|
||||||
LOG.warn("Destination directory not found [%s]", dest_dir)
|
|
||||||
if not o_preview:
|
|
||||||
LOG.info("Creating destination directory [%s]", dest_dir)
|
|
||||||
os.makedirs(dest_dir)
|
|
||||||
if o_gui:
|
|
||||||
gui(src_dir, dest_dir, o_overwrite)
|
|
||||||
else:
|
|
||||||
cli(src_dir, dest_dir, o_preview, o_overwrite)
|
|
||||||
LOG.debug("Done.")
|
|
||||||
return 0
|
|
||||||
except Error, err:
|
|
||||||
LOG.error(err)
|
|
||||||
return 1
|
|
||||||
except Usage, err:
|
|
||||||
LOG.error(err)
|
|
||||||
LOG.info("for usage use -h or --help")
|
|
||||||
return 2
|
|
||||||
|
|
||||||
|
|
||||||
def gui(src_dir, dest_dir, o_overwrite):
|
|
||||||
""" graphical user interface """
|
|
||||||
print src_dir, dest_dir, o_overwrite
|
|
||||||
|
|
||||||
|
|
||||||
def cli(src_dir, dest_dir, o_preview, o_overwrite):
|
|
||||||
""" command line interface """
|
|
||||||
print src_dir, dest_dir, o_preview, o_overwrite
|
|
||||||
|
|
||||||
|
|
||||||
def usage(script_name):
|
|
||||||
print
|
|
||||||
print "usage: %s [options] [src_dir [dest_dir]]" % (script_name,)
|
|
||||||
print """
|
|
||||||
src_dir source directory to search for MOD/MOI
|
|
||||||
dest_dir destination directory for MPG files
|
|
||||||
options:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-f, --force override files with same name in destination directory
|
|
||||||
-g, --gui force interactive mode
|
|
||||||
-p, --preview preview only, don't copy, don't create non-existent directories
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main(sys.argv))
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user