diff --git a/tools/onvifGetStreamUri.js b/tools/onvifGetStreamUri.js new file mode 100644 index 00000000..cebcce0e --- /dev/null +++ b/tools/onvifGetStreamUri.js @@ -0,0 +1,164 @@ +const fetch = require('node-fetch'); +const dgram = require('dgram'); +const parseString = require('xml2js').parseString; +const base64 = require('base-64'); + +const USERNAME = process.argv[2] +const PASSWORD = process.argv[3] + +if(!USERNAME || !PASSWORD){ + console.log(`Missing Username and/or Password!`) + console.log(`Example : node ./onvifGetStreamUri.js "USERNAME" "PASSWORD"`) + console.log(`Put the quotations seen in the example!`) + return +} + +function cleanKeys(obj) { + const cleanKey = (key) => key.includes(':') ? key.split(':').pop() : key; + + if (Array.isArray(obj)) { + return obj.map(cleanKeys); + } else if (obj !== null && typeof obj === 'object') { + return Object.keys(obj).reduce((newObj, key) => { + newObj[cleanKey(key)] = cleanKeys(obj[key]); + return newObj; + }, {}); + } + return obj; +} +async function getStreamUri(deviceXAddr, username, password) { + let envelope = ` + + + + + + `; + + const options = { + method: 'POST', + headers: { + 'Content-Type': 'application/soap+xml', + 'Authorization': 'Basic ' + base64.encode(username + ':' + password), + }, + body: envelope, + }; + + let response = await fetch(deviceXAddr, options); + let body = await response.text(); + + parseString(body, async (err, result) => { + if (err) { + console.error(err); + return; + } + const soapBody = cleanKeys(result).Envelope.Body[0] + if (soapBody.Fault) { + console.log(deviceXAddr,'Not Authorized'); + return; + } + try{ + var profiles = soapBody.GetProfilesResponse[0].Profiles; + }catch(err){ + console.log(err.stack) + console.error(deviceXAddr,`getStreamUri soapBody on ERROR`,JSON.stringify(soapBody,null,3)) + return + } + if (!profiles || !profiles.length) { + console.log(deviceXAddr,'No profiles found'); + return; + } + + const firstProfileToken = profiles[0]['$']['token']; + + envelope = ` + + + + + RTP-Unicast + + RTSP + + + ${firstProfileToken} + + + + `; + + options.body = envelope; + response = await fetch(deviceXAddr, options); + body = await response.text(); + + parseString(body, (err, result) => { + if (err) { + console.error(err); + return; + } + + const uri = result['SOAP-ENV:Envelope']['SOAP-ENV:Body'][0]['trt:GetStreamUriResponse'][0]['trt:MediaUri'][0]['tt:Uri'][0]; + console.log('Stream URI:', uri); + }); + }); +} + +const DISCOVER_MSG = Buffer.from(` + + + http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe + uuid:1000-3000-5000-70000000000000 + + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + urn:schemas-xmlsoap-org:ws:2005:04:discovery + + + + dn:NetworkVideoTransmitter + + + +`); + +const socket = dgram.createSocket('udp4'); +socket.bind(() => { + socket.setBroadcast(true); + socket.setMulticastTTL(128); + socket.addMembership('239.255.255.250'); + socket.send(DISCOVER_MSG, 0, DISCOVER_MSG.length, 3702, '239.255.255.250'); +}); + +socket.on('message', function (message, rinfo) { + parseString(message, (err, result) => { + if (err) { + console.error('Failed to parse XML', err); + return; + } + const cleanJson = cleanKeys(result) + if (!cleanJson.Envelope || !cleanJson.Envelope.Body) { + console.error('Unexpected message format', result); + console.error('cleanJson', cleanJson); + return; + } + + const soapBody = cleanJson.Envelope.Body[0]; + const probeMatches = soapBody.ProbeMatches; + if (!probeMatches) { + console.error('No probe matches in message', soapBody); + return; + } + + const probeMatch = probeMatches[0].ProbeMatch[0]; + if (!probeMatch) { + console.error('No probe match in probe matches', probeMatches); + return; + } + let xAddrs = probeMatch.XAddrs[0]; + console.log('Found ONVIF device', xAddrs); + getStreamUri(xAddrs, USERNAME, PASSWORD) + .catch(err => console.error('Failed to get stream URI', err)); + }); +});