fix strange code (this code should be completly rewritten!)
[enigma2-plugins.git] / partnerbox / src / servicewebts / servicewebts.cpp
1
2 /*******************************************************************************
3  VLC Player Plugin by A. Lätsch 2007
4
5  Modified by Dr. Best
6
7  This is free software; you can redistribute it and/or modify it under
8  the terms of the GNU General Public License as published by the Free
9  Software Foundation; either version 2, or (at your option) any later
10  version.
11 ********************************************************************************/
12
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <string>
17 #include <sys/socket.h>
18 #include <netdb.h>
19 #include <signal.h>
20 #include <time.h>
21 #include "servicewebts.h"
22 #include <lib/base/eerror.h>
23 #include <lib/base/object.h>
24 #include <lib/base/ebase.h>
25 #include <lib/service/service.h>
26 #include <lib/base/init_num.h>
27 #include <lib/base/init.h>
28 #include <lib/dvb/decoder.h>
29
30 #include <lib/dvb/pmt.h>
31
32 #define MAX(a,b) ((a) > (b) ? (a) : (b))
33
34 int VPID = 0;
35 int PID_SET = 0;
36 int APID = 0;
37
38
39 /********************************************************************/
40 /* eServiceFactoryWebTS                                                */
41 /********************************************************************/
42
43 eServiceFactoryWebTS::eServiceFactoryWebTS()
44 {
45         ePtr<eServiceCenter> sc;
46
47         eServiceCenter::getPrivInstance(sc);
48         if (sc)
49         {
50                 std::list<std::string> extensions;
51                 sc->addServiceFactory(eServiceFactoryWebTS::id, this, extensions);
52         }
53 }
54
55 eServiceFactoryWebTS::~eServiceFactoryWebTS()
56 {
57         ePtr<eServiceCenter> sc;
58
59         eServiceCenter::getPrivInstance(sc);
60         if (sc)
61                 sc->removeServiceFactory(eServiceFactoryWebTS::id);
62 }
63
64 DEFINE_REF(eServiceFactoryWebTS)
65
66 // iServiceHandler
67 RESULT eServiceFactoryWebTS::play(const eServiceReference &ref, ePtr<iPlayableService> &ptr)
68 {
69         ptr = new eServiceWebTS(ref);
70         return 0;
71 }
72
73 RESULT eServiceFactoryWebTS::record(const eServiceReference &ref, ePtr<iRecordableService> &ptr)
74 {
75         ptr=0;
76         return -1;
77 }
78
79 RESULT eServiceFactoryWebTS::list(const eServiceReference &, ePtr<iListableService> &ptr)
80 {
81         ptr=0;
82         return -1;
83 }
84
85 RESULT eServiceFactoryWebTS::info(const eServiceReference &ref, ePtr<iStaticServiceInformation> &ptr)
86 {
87         ptr = 0;
88         return -1;
89 }
90
91 RESULT eServiceFactoryWebTS::offlineOperations(const eServiceReference &, ePtr<iServiceOfflineOperations> &ptr)
92 {
93         ptr = 0;
94         return -1;
95 }
96
97
98 /********************************************************************/
99 /* TSAudioInfoWeb                                            */
100 /********************************************************************/
101 DEFINE_REF(TSAudioInfoWeb);
102
103 void TSAudioInfoWeb::addAudio(int pid, std::string lang, std::string desc, int type) {
104         StreamInfo as;
105         as.description = desc;
106         as.language = lang;
107         as.pid = pid;
108         as.type = type;
109         audioStreams.push_back(as);
110 }
111
112
113 /********************************************************************/
114 /* eServiceWebTS                                                       */
115 /********************************************************************/
116
117 eServiceWebTS::eServiceWebTS(const eServiceReference &url): m_pump(eApp, 1)
118 {
119         eDebug("ServiceWebTS construct!");
120         m_filename = url.path.c_str();
121         m_vpid = url.getData(0) == 0 ? 0x44 : url.getData(0);
122         m_apid = url.getData(1) == 0 ? 0x45 : url.getData(1);
123         m_audioInfo = 0;
124         m_destfd = -1;
125 }
126
127 eServiceWebTS::~eServiceWebTS()
128 {
129         eDebug("ServiceWebTS destruct!");
130         stop();
131 }
132
133 DEFINE_REF(eServiceWebTS);
134
135 size_t crop(char *buf)
136 {
137         size_t len = strlen(buf) - 1;
138         while (len > 0 && (buf[len] == '\r' || buf[len] == '\n')) {
139                 buf[len--] = '\0';
140         }
141         return len;
142 }
143
144 static int getline(char** pbuffer, size_t* pbufsize, int fd)
145 {
146         size_t i = 0;
147         int rc;
148         while (true) {
149                 if (i >= *pbufsize) {
150                         char *newbuf = (char*)realloc(*pbuffer, (*pbufsize)+1024);
151                         if (newbuf == NULL)
152                                 return -ENOMEM;
153                         *pbuffer = newbuf;
154                         *pbufsize = (*pbufsize)+1024;
155                 }
156                 rc = ::read(fd, (*pbuffer)+i, 1);
157                 if (rc <= 0 || (*pbuffer)[i] == '\n')
158                 {
159                         (*pbuffer)[i] = '\0';
160                         return rc <= 0 ? -1 : i;
161                 }
162                 if ((*pbuffer)[i] != '\r') i++;
163         }
164 }
165
166
167
168 int eServiceWebTS::openHttpConnection(std::string url)
169 {
170         std::string host;
171         int port = 80;
172         std::string uri;
173
174         int slash = url.find("/", 7);
175         if (slash > 0) {
176                 host = url.substr(7, slash-7);
177                 uri = url.substr(slash, url.length()-slash);
178         } else {
179                 host = url.substr(7, url.length()-7);
180                 uri = "";
181         }
182         int dp = host.find(":");
183         if (dp == 0) {
184                 port = atoi(host.substr(1, host.length()-1).c_str());
185                 host = "localhost";
186         } else if (dp > 0) {
187                 port = atoi(host.substr(dp+1, host.length()-dp-1).c_str());
188                 host = host.substr(0, dp);
189         }
190
191         struct hostent* h = gethostbyname(host.c_str());
192         if (h == NULL || h->h_addr_list == NULL)
193                 return -1;
194         int fd = socket(PF_INET, SOCK_STREAM, 0);
195         if (fd == -1)
196                 return -1;
197
198         struct sockaddr_in addr;
199         addr.sin_family = AF_INET;
200         addr.sin_addr.s_addr = *((in_addr_t*)h->h_addr_list[0]);
201         addr.sin_port = htons(port);
202
203         eDebug("connecting to %s", url.c_str());
204
205         if (connect(fd, (sockaddr*)&addr, sizeof(addr)) == -1) {
206                 std::string msg = "connect failed for: " + url;
207                 eDebug(msg.c_str());
208                 return -1;
209         }
210
211         std::string request = "GET ";
212         request.append(uri).append(" HTTP/1.1\n");
213         request.append("Host: ").append(host).append("\n");
214         request.append("Accept: */*\n");
215         request.append("Connection: close\n");
216         request.append("\n");
217         //eDebug(request.c_str());
218         write(fd, request.c_str(), request.length());
219
220         int rc;
221         size_t buflen = 1000;
222         char* linebuf = (char*)malloc(1000);
223
224         rc = getline(&linebuf, &buflen, fd);
225         //eDebug("RECV(%d): %s", rc, linebuf);
226         if (rc <= 0)
227         {
228                 close(fd);
229                 free(linebuf);
230                 return -1;
231         }
232
233         char proto[100];
234         int statuscode = 0;
235         char statusmsg[100];
236         rc = sscanf(linebuf, "%99s %d %99s", proto, &statuscode, statusmsg);
237         if (rc != 3 || statuscode != 200) {
238                 eDebug("wrong response: \"200 OK\" expected.");
239                 free(linebuf);
240                 close(fd);
241                 return -1;
242         }
243         eDebug("proto=%s, code=%d, msg=%s", proto, statuscode, statusmsg);
244         while (rc > 0)
245         {
246                 rc = getline(&linebuf, &buflen, fd);
247                 //eDebug("RECV(%d): %s", rc, linebuf);
248         }
249         free(linebuf);
250
251         return fd;
252 }
253
254 RESULT eServiceWebTS::connectEvent(const Slot2<void,iPlayableService*,int> &event, ePtr<eConnection> &connection)
255 {
256         connection = new eConnection((iPlayableService*)this, m_event.connect(event));
257         return 0;
258 }
259
260 RESULT eServiceWebTS::start()
261 {
262         ePtr<eDVBResourceManager> rmgr;
263         eDVBResourceManager::getInstance(rmgr);
264         eDVBChannel dvbChannel(rmgr, 0);
265         if (m_destfd == -1)
266         {
267                 m_destfd = ::open("/dev/misc/pvr", O_WRONLY);
268                 if (m_destfd < 0)
269                 {
270                         eDebug("Cannot open /dev/misc/pvr");
271                         return -1;
272                 }
273         }
274         if (dvbChannel.getDemux(m_decodedemux, iDVBChannel::capDecode) != 0) {
275                 eDebug("Cannot allocate decode-demux");
276                 return -1;
277         }
278         if (m_decodedemux->getMPEGDecoder(m_decoder, 1) != 0) {
279                 eDebug("Cannot allocate MPEGDecoder");
280                 return -1;
281         }
282         //m_decoder->setVideoPID(m_vpid, eDVBVideo::MPEG2);
283         //m_decoder->setAudioPID(m_apid, eDVBAudio::aMPEG);
284         m_streamthread = new eStreamThreadWeb();
285         CONNECT(m_streamthread->m_event, eServiceWebTS::recv_event);
286         //m_decoder->freeze(0);
287         //m_decoder->preroll();
288         if (unpause() != 0)
289                 return -1;
290         //m_event(this, evStart);
291         return 0;
292 }
293
294 RESULT eServiceWebTS::stop()
295 {
296         if (m_destfd >= 0)
297         {
298                 ::close(m_destfd);
299                 m_destfd = -1;
300         }
301         printf("TS: %s stop\n", m_filename.c_str());
302         m_streamthread->stop();
303         m_decodedemux->flush();
304         m_audioInfo = 0;
305         APID = 0;
306         VPID = 0;
307         PID_SET = 0;
308         return 0;
309 }
310
311 void eServiceWebTS::recv_event(int evt)
312 {
313         eDebug("eServiceWebTS::recv_event: %d", evt);
314         switch (evt) {
315         case eStreamThreadWeb::evtEOS:
316                 m_decodedemux->flush();
317                 m_event((iPlayableService*)this, evEOF);
318                 break;
319         case eStreamThreadWeb::evtReadError:
320         case eStreamThreadWeb::evtWriteError:
321                 m_decoder->pause();
322                 m_event((iPlayableService*)this, evEOF);
323                 break;
324         case eStreamThreadWeb::evtSOS:
325                 m_event((iPlayableService*)this, evSOF);
326                 break;
327         case eStreamThreadWeb::evtStreamInfo:
328                 if (VPID != 0 && PID_SET == 0 && APID != 0)
329                 {
330                         PID_SET = 1;
331                         m_decodedemux->flush();
332                         m_decoder->setVideoPID(VPID, eDVBVideo::MPEG2);
333                         m_decoder->setAudioPID(APID, eDVBAudio::aMPEG);
334                         m_decoder->pause();
335                         m_event(this, evStart);
336                         m_decoder->play();
337                         
338                 }
339                 bool wasnull = !m_audioInfo;
340                 m_streamthread->getAudioInfo(m_audioInfo);
341                 //if (m_audioInfo)
342                 //      eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
343                 if (m_audioInfo && wasnull) {
344                         eDebug("[ServiceWebTS] %d audiostreams found", m_audioInfo->audioStreams.size());
345                         int sel = getCurrentTrack();
346                         if (sel < 0)
347                                 selectTrack(0);
348                         else if (m_audioInfo->audioStreams[sel].type != eDVBAudio::aMPEG)
349                                 selectTrack(sel);
350                 }
351                 break;
352         }
353 }
354
355 RESULT eServiceWebTS::pause(ePtr<iPauseableService> &ptr)
356 {
357         ptr = this;
358         return 0;
359 }
360
361 // iPausableService
362 RESULT eServiceWebTS::pause()
363 {
364         m_streamthread->stop();
365         m_decoder->pause();
366         return 0;
367 }
368
369 RESULT eServiceWebTS::unpause()
370 {
371         if (!m_streamthread->running())
372         {
373                 int is_streaming = !strncmp(m_filename.c_str(), "http://", 7);
374                 int srcfd = -1;
375                 if (is_streaming)
376                         srcfd = openHttpConnection(m_filename);
377                 else
378                         srcfd = ::open(m_filename.c_str(), O_RDONLY);
379                 if (srcfd < 0) {
380                         eDebug("Cannot open source stream: %s", m_filename.c_str());
381                         return 1;
382                 }
383                 //m_decodedemux->flush();
384                 m_streamthread->start(srcfd, m_destfd);
385                 //m_decoder->unfreeze();
386         }
387         else
388                 eDebug("unpause but thread already running!");
389         return 0;
390 }
391
392 // iSeekableService
393 RESULT eServiceWebTS::seek(ePtr<iSeekableService> &ptr)
394 {
395         ptr = this;
396         return 0;
397 }
398
399 RESULT eServiceWebTS::getLength(pts_t &pts)
400 {
401         return 0;
402 }
403
404 RESULT eServiceWebTS::seekTo(pts_t to)
405 {
406         return 0;
407 }
408
409 RESULT eServiceWebTS::seekRelative(int direction, pts_t to)
410 {
411         return 0;
412 }
413
414 RESULT eServiceWebTS::getPlayPosition(pts_t &pts)
415 {
416         return 0;
417 }
418
419 RESULT eServiceWebTS::setTrickmode(int trick)
420 {
421         return -1;
422 }
423
424 RESULT eServiceWebTS::isCurrentlySeekable()
425 {
426         return 1;
427 }
428
429 RESULT eServiceWebTS::info(ePtr<iServiceInformation>&i)
430 {
431         i = this;
432         return 0;
433 }
434
435 RESULT eServiceWebTS::getName(std::string &name)
436 {
437         name = m_filename;
438         size_t n = name.rfind('/');
439         if (n != std::string::npos)
440                 name = name.substr(n + 1);
441         return 0;
442 }
443
444 int eServiceWebTS::getInfo(int w)
445 {
446         return resNA;
447 }
448
449 std::string eServiceWebTS::getInfoString(int w)
450 {
451         return "";
452 }
453
454 int eServiceWebTS::getNumberOfTracks() {
455         if (m_audioInfo)
456                 return (int)m_audioInfo->audioStreams.size();
457         else
458                 return 0;
459 }
460
461 RESULT eServiceWebTS::selectTrack(unsigned int i) {
462         if (m_audioInfo) {
463                 m_apid = m_audioInfo->audioStreams[i].pid;
464                 eDebug("[ServiceWebTS] audio track %d PID 0x%02x type %d\n", i, m_apid, m_audioInfo->audioStreams[i].type);
465                 m_decoder->setAudioPID(m_apid, m_audioInfo->audioStreams[i].type);
466                 m_decoder->set();
467                 return 0;
468         } else {
469                 return -1;
470         }
471 }
472
473 RESULT eServiceWebTS::getTrackInfo(struct iAudioTrackInfo &info, unsigned int n) {
474         if (m_audioInfo) {
475                 info.m_pid = m_audioInfo->audioStreams[n].pid;
476                 info.m_description = m_audioInfo->audioStreams[n].description;
477                 info.m_language = m_audioInfo->audioStreams[n].language;
478                 return 0;
479         } else {
480                 return -1;
481         }
482 }
483
484 int eServiceWebTS::getCurrentTrack() {
485         if (m_audioInfo) {
486                 for (size_t i = 0; i < m_audioInfo->audioStreams.size(); i++) {
487                         if (m_apid == m_audioInfo->audioStreams[i].pid) {
488                                 return i;
489                         }
490                 }
491         }
492         return -1;
493 }
494
495 /********************************************************************/
496 /* eStreamThreadWeb                                                       */
497 /********************************************************************/
498
499 DEFINE_REF(eStreamThreadWeb)
500
501 eStreamThreadWeb::eStreamThreadWeb(): m_messagepump(eApp, 0) {
502         CONNECT(m_messagepump.recv_msg, eStreamThreadWeb::recvEvent);
503 }
504 eStreamThreadWeb::~eStreamThreadWeb() {
505 }
506
507 void eStreamThreadWeb::start(int srcfd, int destfd) {
508         m_srcfd = srcfd;
509         m_destfd = destfd;
510         m_stop = false;
511         m_audioInfo = 0;
512         run(IOPRIO_CLASS_RT);
513 }
514
515 void eStreamThreadWeb::stop() {
516         m_stop = true;
517         kill();
518 }
519
520 void eStreamThreadWeb::recvEvent(const int &evt)
521 {
522         m_event(evt);
523 }
524
525 RESULT eStreamThreadWeb::getAudioInfo(ePtr<TSAudioInfoWeb> &ptr)
526 {
527         ptr = m_audioInfo;
528         return 0;
529 }
530
531 #define REGISTRATION_DESCRIPTOR 5
532 #define LANGUAGE_DESCRIPTOR 10
533
534 std::string eStreamThreadWeb::getDescriptor(unsigned char buf[], int buflen, int type)
535 {
536         int desc_len;
537         while (buflen > 1) {
538                 desc_len = buf[1];
539                 if (buf[0] == type) {
540                         char str[21];
541                         if (desc_len > 20) desc_len = 20;
542                         strncpy(str, (char*)buf+2, desc_len);
543                         str[desc_len] = '\0';
544                         return std::string(str);
545                 } else {
546                         buflen -= desc_len+2;
547                         buf += desc_len+2;
548                 }
549         }
550         return "";
551 }
552
553 bool eStreamThreadWeb::scanAudioInfo(unsigned char buf[], int len)
554 {
555         if (len < 1880)
556                 return false;
557
558         int adaptfield, pmtpid, offset;
559         unsigned char pmt[1188];
560         int pmtsize = 0;
561
562         for (int a=0; a < len - 188*4; a++) {
563                 if ( buf[a] != 0x47 || buf[a + 188] != 0x47 || buf[a + 376] != 0x47 )
564                         continue; // TS Header
565
566                 if ((0x40 & buf[a + 1]) == 0) // start
567                         continue;
568
569                 if ((0xC0 & buf[a + 3]) != 0) // scrambling
570                         continue;
571
572                 adaptfield = (0x30 & buf[a + 3]) >> 4;
573
574                 if ((adaptfield & 1) == 0) // adapt - no payload
575                         continue;
576
577                 offset = adaptfield == 3 ? 1 + (0xFF & buf[a + 4]) : 0; //adaptlength
578
579                 if (buf[a + offset + 4] != 0 || buf[a + offset + 5] != 2 || (0xF0 & buf[a + offset + 6]) != 0xB0)
580                 {
581                         a += 187;
582                         continue;
583                 }
584
585                 pmtpid = (0x1F & buf[a + 1])<<8 | (0xFF & buf[a + 2]);
586                 memcpy(pmt + pmtsize, buf + a + 4 + offset, 184 - offset);
587                 pmtsize += 184 - offset;
588
589                 if (pmtsize >= 1000)
590                         break;
591         }
592
593         if (pmtsize == 0) return false;
594
595         int pmtlen = (0x0F & pmt[2]) << 8 | (0xFF & pmt[3]);
596         std::string lang;
597         std::string pd_type;
598         ePtr<TSAudioInfoWeb> ainfo = new TSAudioInfoWeb();
599
600         for (int b=8; b < pmtlen-4 && b < pmtsize-6; b++)
601         {
602                 if ( (0xe0 & pmt[b+1]) != 0xe0 )
603                         continue;
604
605                 int pid = (0x1F & pmt[b+1])<<8 | (0xFF & pmt[b+2]);
606                 switch(pmt[b])
607                 {
608                 case 1:
609                 case 2: // MPEG Video
610                         //addVideo(pid, "MPEG2");
611                         if (VPID == 0)
612                                 VPID= pid;
613                         break;
614
615                 case 0x1B: // H.264 Video
616                         //addVideo(pid, "H.264");
617                         break;
618                 case 3:
619                 case 4: // MPEG Audio
620                         if (APID == 0)
621                                 APID =pid;
622                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
623                         ainfo->addAudio(pid, lang, "MPEG", eDVBAudio::aMPEG);
624                         break;
625
626                 case 0x80:
627                 case 0x81:  //private data of AC3 in ATSC
628                 case 0x82:
629                 case 0x83:
630                 case 6:
631                         lang = getDescriptor(pmt+b+5, pmt[b+4], LANGUAGE_DESCRIPTOR);
632                         pd_type = getDescriptor(pmt+b+5, pmt[b+4], REGISTRATION_DESCRIPTOR);
633                         //if (pd_type == "AC-3")
634                         // dirty dirty :-) Aber es funktioniert...
635                         if (lang.length() != 0)
636                                 ainfo->addAudio(pid, lang, "AC-3", eDVBAudio::aAC3);
637                         break;
638                 }
639                 b += 4 + pmt[b+4];
640         }
641         if (ainfo->audioStreams.size() > 0) {
642                 m_audioInfo = ainfo;
643                 return true;
644         } else {
645                 return false;
646         }
647 }
648
649 void eStreamThreadWeb::thread() {
650         const int bufsize = 40000;
651         unsigned char buf[bufsize];
652         bool eof = false;
653         fd_set rfds;
654         fd_set wfds;
655         struct timeval timeout;
656         int rc,r,w,maxfd;
657         time_t next_scantime = 0;
658         bool sosSend = false;
659         m_running = true;
660
661         r = w = 0;
662         hasStarted();
663         eDebug("eStreamThreadWeb started");
664         while (!m_stop) {
665                 pthread_testcancel();
666                 FD_ZERO(&rfds);
667                 FD_ZERO(&wfds);
668                 maxfd = 0;
669                 timeout.tv_sec = 1;
670                 timeout.tv_usec = 0;
671                 if (r < bufsize) {
672                         FD_SET(m_srcfd, &rfds);
673                         maxfd = MAX(maxfd, m_srcfd);
674                 }
675                 if (w < r) {
676                         FD_SET(m_destfd, &wfds);
677                         maxfd = MAX(maxfd, m_destfd);
678                 }
679                 rc = select(maxfd+1, &rfds, &wfds, NULL, &timeout);
680                 if (rc == 0) {
681                         eDebug("eStreamThreadWeb::thread: timeout!");
682                         continue;
683                 }
684                 if (rc < 0) {
685                         eDebug("eStreamThreadWeb::thread: error in select (%d)", errno);
686                         break;
687                 }
688                 if (FD_ISSET(m_srcfd, &rfds)) {
689                         rc = ::read(m_srcfd, buf+r, bufsize - r);
690                         if (rc < 0) {
691                                 eDebug("eStreamThreadWeb::thread: error in read (%d)", errno);
692                                 m_messagepump.send(evtReadError);
693                                 break;
694                         } else if (rc == 0) {
695                                 eof = true;
696                         } else {
697                                 if (!sosSend) {
698                                         sosSend = true;
699                                         m_messagepump.send(evtSOS);
700                                 }
701                                 r += rc;
702                                 if (r == bufsize) eDebug("eStreamThreadWeb::thread: buffer full");
703                         }
704                 }
705                 if (FD_ISSET(m_destfd, &wfds) && (w < r) && ((r > bufsize/4) || eof)) {
706                         rc = ::write(m_destfd, buf+w, r-w);
707                         if (rc < 0) {
708                                 eDebug("eStreamThreadWeb::thread: error in write (%d)", errno);
709                                 m_messagepump.send(evtWriteError);
710                                 break;
711                         }
712                         w += rc;
713                         //eDebug("eStreamThreadWeb::thread: buffer r=%d w=%d",r,w);
714                         if (w == r) {
715                                 if (time(0) >= next_scantime) {
716                                         if (scanAudioInfo(buf, r)) {
717                                                 m_messagepump.send(evtStreamInfo);
718                                                 next_scantime = time(0) + 1;
719                                         }
720                                 }
721                                 w = r = 0;
722                         }
723                 }
724                 if (eof && (r==w)) {
725                         m_messagepump.send(evtEOS);
726                         break;
727                 }
728         }
729         eDebug("eStreamThreadWeb end");
730 }
731
732 void eStreamThreadWeb::thread_finished() {
733         if (m_srcfd >= 0)
734                 ::close(m_srcfd);
735         eDebug("eStreamThreadWeb closed");
736         m_running = false;
737 }
738
739 eAutoInitPtr<eServiceFactoryWebTS> init_eServiceFactoryWebTS(eAutoInitNumbers::service+1, "eServiceFactoryWebTS");
740
741 PyMODINIT_FUNC
742 initservicewebts(void)
743 {
744         Py_InitModule("servicewebts", NULL);
745 }