start hugin script
This commit is contained in:
parent
1ea7d655db
commit
a35fcce99b
|
@ -13,262 +13,138 @@ import minimist from 'minimist';
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
|
|
||||||
// configs
|
// configs
|
||||||
let sequence_name: string = '3596977'
|
|
||||||
let folders_list_txt: any = [
|
|
||||||
"3596249",
|
|
||||||
"3596253", "3596977",
|
|
||||||
"3675557", "3675577", "3716345", "4456921", "4457241", "4457257",
|
|
||||||
// "4457273", // à récupérer
|
|
||||||
"4457289", "4457689", "4457705", "4539913", "4539929", "4539945", "4559529",
|
|
||||||
"4559545", "4559561", "4559577", "4559593", "4559609", "4559625", "4559641", "4559657", "4559673", "4559689", "4559705", "4559721", "4559737", "4559753",
|
|
||||||
"4559769", "4559785", "4559801", "4559817", "4559833", "4559849", "4559865", "4904153", "4904233", "4904249", "4904265", "4904281", "4904297", "4904313",
|
|
||||||
"4904329", "4904345", "4904361", "4904377", "4904393", "5192937", "5193161", "5193177", "5193193", "5193209", "5193225", "5193321", "5193337", "5193417",
|
|
||||||
"5193433",
|
|
||||||
// "5193449",
|
|
||||||
"5201385", "5213385", "5213481", "5213529", "5234665", "5234857", "5234889", "5235001", "5324265", "5775977", "5775993",
|
|
||||||
// "5776009",
|
|
||||||
"5776025",
|
|
||||||
"6081417", "6081433", "6081449", "6081465", "6081689", "6081705", "6092281", "6092313", "6101305", "6102457", "6102473", "6102489",
|
|
||||||
// "6102521",
|
|
||||||
"6102537", "6102569", "6127433", "6133465", "6242729", "6329129", "6329177", "6329241", "6329305", "6329321", "6738633", "6738809", "6738953", "6752761",
|
|
||||||
"6752889",
|
|
||||||
"6752905",
|
|
||||||
"6752921",
|
|
||||||
"6752937",
|
|
||||||
"6752953",
|
|
||||||
"6760985", "6761049", "6761289",
|
|
||||||
"6794633",
|
|
||||||
"7325929", "7326457", "7328265", "7330009", "7330025",
|
|
||||||
"7330041",
|
|
||||||
"7330057"
|
|
||||||
]
|
|
||||||
|
|
||||||
let enable_write_gpx_file = true;
|
let dossier_pto_output = "~/www/scripts/hugin-gopro-fusion/output_pto";
|
||||||
let just_one_photo_in_folder = false;
|
|
||||||
let folder = "/home/poule/encrypted/stockage-syncable/photos/imagerie kartaview carto tel/kartaview_export_storage/share2tykayn/"
|
|
||||||
let folder_photos = folder + "photo"
|
|
||||||
let dossier_gpx: string = "./output_gpx"
|
|
||||||
let file_gpx: string = "3596249_d875a_60a0f9bf38f99.txt"
|
|
||||||
|
|
||||||
|
let absolutePath = "/home/cipherbliss/www/scripts/hugin-gopro-fusion/img";
|
||||||
|
let folder = "~/www/scripts/hugin-gopro-fusion/img"
|
||||||
|
let subFolder = "essai"
|
||||||
let mini_arguments: any = minimist(process.argv.slice(2))
|
let mini_arguments: any = minimist(process.argv.slice(2))
|
||||||
console.log('mini_arguments', mini_arguments)
|
console.log('mini_arguments', mini_arguments)
|
||||||
|
|
||||||
if (mini_arguments['sequence']) {
|
|
||||||
sequence_name = mini_arguments['sequence']
|
|
||||||
}
|
|
||||||
if (mini_arguments['folder']) {
|
if (mini_arguments['folder']) {
|
||||||
folder = mini_arguments['folder']
|
folder = mini_arguments['folder']
|
||||||
}
|
}
|
||||||
if (mini_arguments['gpx-output']) {
|
|
||||||
dossier_gpx = mini_arguments['gpx-output']
|
|
||||||
}
|
|
||||||
if (mini_arguments['gpx-input']) {
|
|
||||||
file_gpx = mini_arguments['gpx-input']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function findMinMaxNumberOfPhotos(filesList:Array<string>){
|
||||||
// let dossier_photo: string = folder + "test_photo_apply"
|
return {
|
||||||
let dossier_photo: string = folder_photos
|
min: '080096',
|
||||||
let dossier_gpx_input: string = folder + "metadata_file"
|
max: '080096',
|
||||||
let path_gpx_input: string = dossier_gpx_input + "/" + sequence_name + "/" + file_gpx
|
|
||||||
|
|
||||||
if (mini_arguments['gpx-input-path']) {
|
|
||||||
path_gpx_input = mini_arguments['gpx-input-path']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let gpxData: any = {}
|
|
||||||
let exif_commands: any = ['#!/bin/bash', '# apply exif data to photos']
|
|
||||||
|
|
||||||
|
|
||||||
function makeGpxFromKartaview(tableKartaviewTrace: any) {
|
|
||||||
|
|
||||||
console.log('make gpx')
|
|
||||||
let track_points: string = '';
|
|
||||||
|
|
||||||
tableKartaviewTrace.forEach((elem: any) => {
|
|
||||||
// console.log('elem', elem)
|
|
||||||
// console.log('elem', elem[6] *1000)
|
|
||||||
if (elem.length && elem[6]) {
|
|
||||||
let utc_time = new Date(elem[6] * 1000)
|
|
||||||
|
|
||||||
// console.log('utc_time', utc_time.toISOString())
|
|
||||||
track_points = `${track_points}<trkpt lat="${elem[0]}" lon="${elem[1]}">
|
|
||||||
<ele>${elem[2]}</ele>
|
|
||||||
<time>${utc_time.toISOString()}</time>
|
|
||||||
</trkpt>
|
|
||||||
`
|
|
||||||
}else{
|
|
||||||
console.log('!!!!!! makeGpxFromKartaview no good element in gpx', elem)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
let content = `<?xml version='1.0' encoding='UTF-8'?>
|
|
||||||
<gpx version="1.1" creator="JOSM GPX export" xmlns="http://www.topografix.com/GPX/1/1"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
|
||||||
<metadata>
|
|
||||||
<desc>trace_gpx_de_demo</desc>
|
|
||||||
<author>
|
|
||||||
<name>somebody</name>
|
|
||||||
</author>
|
|
||||||
<copyright author="somebody">
|
|
||||||
<year>2023</year>
|
|
||||||
<license>https://creativecommons.org/licenses/by-sa/2.5</license>
|
|
||||||
</copyright>
|
|
||||||
<keywords>nada</keywords>
|
|
||||||
</metadata>
|
|
||||||
<trk>
|
|
||||||
<trkseg>
|
|
||||||
${track_points}
|
|
||||||
</trkseg>
|
|
||||||
</trk>
|
|
||||||
</gpx>`
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
}
|
||||||
|
function getGoproFiles(folder:string){
|
||||||
|
return fs.readdirSync(folder)
|
||||||
|
}
|
||||||
|
function makeBashScriptHugin(minmax:any){
|
||||||
|
|
||||||
function reduceGpxPointsToInterval(sequence_name: any) {
|
// in each increment of a pair of photos, build a pto referencing absolute paths
|
||||||
|
// write the pto in output
|
||||||
|
let currentNumber = '123';
|
||||||
|
let ptoContent = makePto(absolutePath, currentNumber);
|
||||||
|
writeFile('merging_'+currentNumber, ptoContent)
|
||||||
|
}
|
||||||
|
function makePto(absolutePath:string,currentNumber:string ){
|
||||||
|
|
||||||
let photos_count_in_sequence = photo_folders_counter[sequence_name].length
|
let front_picture_name = absolutePath+'GF'+currentNumber+'.JPG';
|
||||||
let interval = Math.round(gpxData[sequence_name].length / photos_count_in_sequence)
|
let back_picture_name = absolutePath+'GB'+currentNumber+'.JPG';
|
||||||
let ii = 1;
|
|
||||||
console.log('--------- sequence_name', sequence_name)
|
|
||||||
console.log('--------- nombre de photos ', photos_count_in_sequence)
|
|
||||||
console.log('--------- nombre de points gpx ', gpxData[sequence_name].length)
|
|
||||||
console.log('--------- interval: prendre une donnée gps tous les ', interval, 'points de la trace de séquence')
|
|
||||||
let jj = 0
|
|
||||||
let photo_counter = 0
|
|
||||||
gpxData[sequence_name].forEach((elem: any) => {
|
|
||||||
// only work on an interval of files to assign gps coordinates
|
|
||||||
jj++
|
|
||||||
ii--
|
|
||||||
if (ii <= 0) {
|
|
||||||
// set the gps coordinates to the picture in sequence
|
|
||||||
let timestamp = elem[6] * 1000
|
|
||||||
let lat = elem[0]
|
|
||||||
let lon = elem[1]
|
|
||||||
let date = new Date(timestamp)
|
|
||||||
let date_simezone_indicator = ''; // +2:00
|
|
||||||
let iso = moment(date).format('YYYY:MM:DD HH:mm:ss') + date_simezone_indicator
|
|
||||||
|
|
||||||
ii = interval * 1
|
|
||||||
|
|
||||||
let file_photo_name = photo_folders_counter[sequence_name][photo_counter]
|
|
||||||
|
|
||||||
|
|
||||||
if (file_photo_name) {
|
console.log('front_picture_name', front_picture_name)
|
||||||
// set exif data
|
console.log('back_picture_name', front_picture_name)
|
||||||
// console.log('file_photo_name to apply exif data', photo_counter, file_photo_name)
|
|
||||||
|
return `
|
||||||
|
# hugin project file
|
||||||
|
#hugin_ptoversion 2
|
||||||
|
p f2 w5662 h2831 v360 k0 E12.9717 R0 n"TIFF_m c:LZW r:CROP"
|
||||||
|
m i0
|
||||||
|
|
||||||
|
# image lines
|
||||||
|
#-hugin autoCenterCrop=1 cropFactor=3.66667
|
||||||
|
i w3104 h3000 f2 v187.621680193473 Ra0.929120004177094 Rb-2.97991991043091 Rc0.832589983940125 Rd-1.6331000328064 Re0.223350003361702 Eev12.97168 Er1 Eb1 r0 p0 y0 TrX0 TrY0 TrZ0 Tpy0 Tpp0 j0 a0 b0 c0 d0 e0 g0 t0 Va1 Vb0 Vc0 Vd0 Vx0 Vy0 S56,3048,0,3000 Vm5 n"${back_picture_name}"
|
||||||
|
#-hugin autoCenterCrop=1 cropFactor=3.66666674613953
|
||||||
|
i w3104 h3000 f2 v200.312016860549 Ra0.929120004177094 Rb-2.97991991043091 Rc0.832589983940125 Rd-1.6331000328064 Re0.223350003361702 Eev12.86747 Er1 Eb1 r-0.321229885118998 p0.498637821558678 y-179.883320935885 TrX0 TrY0 TrZ0 Tpy0 Tpp0 j0 a0 b0 c0 d0 e0 g0 t0 Va1 Vb0 Vc0 Vd0 Vx0 Vy0 S56,3048,0,3000 Vm5 n"${front_picture_name}"
|
||||||
|
|
||||||
|
|
||||||
// -File:ModificationDateTime=="${iso}" \\
|
# specify variables that should be optimized
|
||||||
// -File:CreateDate="${iso}" \\
|
v v0
|
||||||
let exif_command = `\n
|
v Ra0
|
||||||
exiftool -overwrite_original \\
|
v Rb0
|
||||||
-GPSDateTime="${iso}" \\
|
v Rc0
|
||||||
-AllDates="${iso}" \\
|
v Rd0
|
||||||
-MediaCreateDate="${iso}" \\
|
v Re0
|
||||||
-MediaModifyDate="${iso}" \\
|
v Vb0
|
||||||
-TrackCreateDate=="${iso}" \\
|
v Vc0
|
||||||
-TrackModifyDate=="${iso}" \\
|
v Vd0
|
||||||
-GPSLongitude="${lon}" \\
|
v v1
|
||||||
-GPSLatitude="${lat}" \\
|
v Ra1
|
||||||
"${dossier_photo + '/' + sequence_name + '/' + file_photo_name}"
|
v Rb1
|
||||||
|
v Rc1
|
||||||
|
v Rd1
|
||||||
|
v Re1
|
||||||
|
v Eev1
|
||||||
|
v r1
|
||||||
|
v p1
|
||||||
|
v y1
|
||||||
|
v Vb1
|
||||||
|
v Vc1
|
||||||
|
v Vd1
|
||||||
|
v
|
||||||
|
|
||||||
|
|
||||||
|
# control points
|
||||||
|
c n0 N1 x931 y224 X2196 Y185 t0
|
||||||
|
c n0 N1 x2488.00001630569 y524.000007066704 X514.860630764196 Y399.786974637271 t0
|
||||||
|
c n0 N1 x2863.00001478066 y830.000009619205 X310 Y843 t0
|
||||||
|
c n0 N1 x2912.00000631564 y1960.00001513932 X165.422047127648 Y1942.23480647153 t0
|
||||||
|
c n0 N1 x109.000013207601 y1959.00001112345 X2860.90269812833 Y1909.02844555244 t0
|
||||||
|
c n0 N1 x866.000016448824 y2711.00000028952 X2283.94256385574 Y2796.23681774024 t0
|
||||||
|
c n0 N1 x121.000003370916 y993.000018856411 X2832.23948711881 Y1047.9747652351 t0
|
||||||
|
|
||||||
|
# masks
|
||||||
|
k i0 t1 p"252 628 484 351 745 156 1008 41 1121 2 1992 4 2289 127 2571 317 2769 515 2949 795 3050 1087 3095 1282 3105 1673 3050 1932 2938 2213 2766 2482 2610 2646 2401 2803 2179 2917 1984 2985 1879 3006 1251 3011 1079 2970 815 2860 573 2706 385 2531 231 2328 106 2080 38 1859 -5 1600 -8 1311 80 969 109 878"
|
||||||
|
k i1 t1 p"252 628 484 351 745 156 1008 41 1121 2 1992 4 2289 127 2571 317 2769 515 2949 795 3050 1087 3095 1282 3105 1673 3050 1932 2925 2205 2766 2482 2610 2646 2401 2803 2179 2917 1984 2985 1879 3006 1251 3011 1079 2970 815 2860 573 2706 385 2531 231 2328 106 2080 38 1859 -5 1600 -8 1311 80 969 109 878"
|
||||||
|
|
||||||
|
#hugin_optimizeReferenceImage 0
|
||||||
|
#hugin_blender internal
|
||||||
|
#hugin_remapper nona
|
||||||
|
#hugin_enblendOptions
|
||||||
|
#hugin_enfuseOptions
|
||||||
|
#hugin_hdrmergeOptions -m avg -c
|
||||||
|
#hugin_verdandiOptions --seam=blend
|
||||||
|
#hugin_edgeFillMode 0
|
||||||
|
#hugin_edgeFillKeepInput false
|
||||||
|
#hugin_outputLDRBlended true
|
||||||
|
#hugin_outputLDRLayers false
|
||||||
|
#hugin_outputLDRExposureRemapped false
|
||||||
|
#hugin_outputLDRExposureLayers false
|
||||||
|
#hugin_outputLDRExposureBlended false
|
||||||
|
#hugin_outputLDRStacks false
|
||||||
|
#hugin_outputLDRExposureLayersFused false
|
||||||
|
#hugin_outputHDRBlended false
|
||||||
|
#hugin_outputHDRLayers false
|
||||||
|
#hugin_outputHDRStacks false
|
||||||
|
#hugin_outputLayersCompression LZW
|
||||||
|
#hugin_outputImageType jpg
|
||||||
|
#hugin_outputImageTypeCompression LZW
|
||||||
|
#hugin_outputJPEGQuality 90
|
||||||
|
#hugin_outputImageTypeHDR exr
|
||||||
|
#hugin_outputImageTypeHDRCompression LZW
|
||||||
|
#hugin_outputStacksMinOverlap 0.7
|
||||||
|
#hugin_outputLayersExposureDiff 0.5
|
||||||
|
#hugin_outputRangeCompression 0
|
||||||
|
#hugin_optimizerMasterSwitch 6
|
||||||
|
#hugin_optimizerPhotoMasterSwitch 21
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
||||||
// -GPSLongitudeRef="West" \
|
|
||||||
// -GPSLatitudeRef="North" \
|
|
||||||
// exiftool -overwrite_original '-AllDates<gpsdatetime' "${dossier_photo+'/'+sequence_name+'/'+file_photo_name}"
|
|
||||||
|
|
||||||
exif_commands.push(exif_command)
|
|
||||||
}
|
|
||||||
photo_counter++
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
exif_commands.push(`geovisio upload --api-url https://panoramax.openstreetmap.fr "${dossier_photo}/${sequence_name}"`)
|
|
||||||
let exif_bash_file = exif_commands.join('\n')
|
|
||||||
|
|
||||||
writeFile(`_exif_commmand_sequence_${sequence_name}.sh`, exif_bash_file)
|
|
||||||
|
|
||||||
console.log('\n ----- apply exif data:', `\n bash ${dossier_gpx}/${exif_bash_file}`)
|
|
||||||
|
|
||||||
console.log('\n\nDONE for sequence_name', sequence_name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function openKartaviewTxtGPX(filepath: any, sequence_name: string) {
|
|
||||||
console.log('openKartaviewTxtGPX filepath', filepath)
|
|
||||||
let boom = filepath.split('/')
|
|
||||||
let fileName = boom[boom.length - 1]
|
|
||||||
console.log('openKartaviewTxtGPX fileName', fileName,)
|
|
||||||
fs.readFile(filepath, 'utf8', (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
const gpx_content = data;
|
|
||||||
let lines = gpx_content.split('\n')
|
|
||||||
console.log('lines.length', lines.length)
|
|
||||||
|
|
||||||
let tableKartaviewTrace: any = [];
|
|
||||||
|
|
||||||
// loop on all lines, only take the lines that contain :g:
|
|
||||||
lines.forEach((elem: any) => {
|
|
||||||
if (elem.indexOf(":g:") > -1) {
|
|
||||||
// do stuff on gpxData to enrich it
|
|
||||||
let boom = elem.split(':')
|
|
||||||
let timestamp = boom[0]
|
|
||||||
|
|
||||||
let gpsmodel = boom[2]
|
|
||||||
let gps = gpsmodel.split(';')
|
|
||||||
gps.push(timestamp)
|
|
||||||
tableKartaviewTrace.push(gps)
|
|
||||||
// let date = new Date(boom[0] * 1000)
|
|
||||||
// console.log('*', date, gps[0], gps[1])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
gpxData[sequence_name] = [...tableKartaviewTrace]
|
|
||||||
|
|
||||||
|
|
||||||
console.log(' makeGpxFromKartaview for sequence ', sequence_name)
|
|
||||||
let content_gpx = makeGpxFromKartaview(tableKartaviewTrace)
|
|
||||||
|
|
||||||
|
|
||||||
if (enable_write_gpx_file) {
|
|
||||||
|
|
||||||
writeFile('' + fileName + '_trace.gpx', content_gpx)
|
|
||||||
}
|
|
||||||
if (Object.keys(gpxData).length === folders_list_txt.length) {
|
|
||||||
gather()
|
|
||||||
}
|
|
||||||
// console.log('gpx_content', gpx_content)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let cwd = path.dirname(process.cwd()) + '/' + path.basename(process.cwd())
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get a list of all files in a path
|
|
||||||
* @param folderPath
|
|
||||||
*/
|
|
||||||
async function getAllFilesInFolder(folderPath: string) {
|
|
||||||
let filesList: any = []
|
|
||||||
|
|
||||||
console.log('------ reading folder ', folderPath)
|
|
||||||
filesList = fs.readdirSync(folderPath);
|
|
||||||
if (just_one_photo_in_folder) {
|
|
||||||
filesList = [filesList.shift()]
|
|
||||||
}
|
|
||||||
return filesList;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function writeFile(fileName: string, fileContent: any) {
|
function writeFile(fileName: string, fileContent: any) {
|
||||||
console.log('write file', fileName)
|
console.log('write file', fileName)
|
||||||
|
|
||||||
return fs.writeFile(
|
return fs.writeFile(
|
||||||
`${dossier_gpx}/${fileName}`,
|
`${dossier_pto_output}/${fileName}`,
|
||||||
fileContent,
|
fileContent,
|
||||||
'utf8',
|
'utf8',
|
||||||
(err) => {
|
(err) => {
|
||||||
|
@ -279,64 +155,15 @@ function writeFile(fileName: string, fileContent: any) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let photo_folders_counter: any = {}
|
function main(){
|
||||||
|
|
||||||
function listPhotos(sequence_number: string) {
|
let filesOfFolder = getGoproFiles(folder+subFolder)
|
||||||
console.log('------- listPhotos dossier_photo', dossier_photo + '/' + sequence_number)
|
let minmax = findMinMaxNumberOfPhotos(filesOfFolder)
|
||||||
let photo_folder = dossier_photo + '/' + sequence_number;
|
for (let ii = minmax.min; ii < minmax.max; ii++){
|
||||||
getAllFilesInFolder(photo_folder)
|
|
||||||
.then(listOfFiles => {
|
|
||||||
photo_folders_counter[sequence_number] = listOfFiles
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getGPXAndEnrichExifOfPhotosInFolder(sequence_number: string) {
|
makePto(absolutePath, ii)
|
||||||
|
|
||||||
// get metadata txt file in metadata folder
|
|
||||||
getAllFilesInFolder(dossier_gpx_input + '/' + sequence_number)
|
|
||||||
.then(listOfFiles => {
|
|
||||||
|
|
||||||
console.log('listOfFiles', listOfFiles.length)
|
|
||||||
// console.log('listOfFiles', listOfFiles)
|
|
||||||
listOfFiles.forEach((jpg_file: string) => {
|
|
||||||
openKartaviewTxtGPX(dossier_gpx_input + '/' + sequence_number + '/' + jpg_file, sequence_number)
|
|
||||||
})
|
|
||||||
}, err => {
|
|
||||||
throw new Error(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
listPhotos(sequence_number)
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* runs after all sequences have gotten their photo and gpx data
|
|
||||||
*/
|
|
||||||
function gather() {
|
|
||||||
|
|
||||||
|
|
||||||
// console.log('gpxData', gpxData)
|
|
||||||
console.log('sequences', folders_list_txt)
|
|
||||||
folders_list_txt.forEach((sequence: string) => {
|
|
||||||
|
|
||||||
reduceGpxPointsToInterval(sequence)
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
|
|
||||||
console.log('dossiers à traiter: ', folders_list_txt.length)
|
|
||||||
folders_list_txt.forEach((elem: string) => {
|
|
||||||
getGPXAndEnrichExifOfPhotosInFolder(elem).then(r => {
|
|
||||||
console.log('r', r)
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// run it all
|
// run it all
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue