Ticket #3580: patchfile.6

File patchfile.6, 44.1 KB (added by robsbox@…, 17 years ago)

fix small memory leak & iterator deficiency

Line 
1Index: libs/libmythtv/programinfo.h
2===================================================================
3--- libs/libmythtv/programinfo.h        (revision 13462)
4+++ libs/libmythtv/programinfo.h        (working copy)
5@@ -12,6 +12,7 @@
6 using namespace std;
7 typedef QMap<long long, long long> frm_pos_map_t;
8 typedef QMap<long long, int> frm_dir_map_t;
9+typedef QMap<long long, time_t> pos_time_map_t;
10 
11 #define NUMPROGRAMLINES 43
12 
13@@ -240,6 +241,7 @@
14     void SetPositionMap(frm_pos_map_t &, int type,
15                         long long min_frm = -1, long long max_frm = -1) const;
16     void SetPositionMapDelta(frm_pos_map_t &, int type) const;
17+    int  GetTrackMap( const QString &sChanId, const QString &sStartTime, pos_time_map_t &TrackMap);
18 
19 
20     // GUI stuff
21@@ -397,3 +399,4 @@
22 #endif
23 
24 /* vim: set expandtab tabstop=4 shiftwidth=4: */
25+
26Index: libs/libmythtv/programinfo.cpp
27===================================================================
28--- libs/libmythtv/programinfo.cpp      (revision 13462)
29+++ libs/libmythtv/programinfo.cpp      (working copy)
30@@ -2747,6 +2747,61 @@
31     }
32 }
33 
34+/////////////////////////////////////////////////////////////////////////////
35+//                 
36+// GetTrackMap - creates a position map <position offset, time> indicating the
37+//                     ending position of each commercial break in a recording
38+//                     returns the number of tracks
39+//                 
40+/////////////////////////////////////////////////////////////////////////////
41+
42+int ProgramInfo::GetTrackMap ( const QString &sChanId, const QString &sStartTime, pos_time_map_t &trackMap )
43+{
44+
45+    frm_dir_map_t markmap;
46+    frm_pos_map_t posMap;
47+    int keyframe_dist = 15;
48+
49+    trackMap.clear();
50+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( sChanId, sStartTime );
51+
52+    if (pInfo == NULL)
53+    {
54+        VERBOSE( VB_UPNP, QString( "ProgramInfo::GetTrackMap - GetProgramFromRecorded( %1, %2 ) returned NULL" )
55+                                 .arg( sChanId )
56+                                 .arg( sStartTime ));
57+        return 0;
58+    }
59+
60+    pInfo->GetMarkupMap(markmap, MARK_COMM_END);
61+
62+    pInfo->GetPositionMap(posMap, MARK_GOP_START);
63+
64+    if ( posMap.isEmpty() ) {
65+       pInfo->GetPositionMap(posMap, MARK_GOP_BYFRAME);
66+       keyframe_dist = 1;
67+    }
68+
69+    delete pInfo;
70+
71+    if ( markmap.isEmpty() || posMap.isEmpty() )
72+    {
73+        VERBOSE( VB_UPNP, "ProgramInfo::GetTrackMap - No Commercial End Marks.");
74+        return 0;
75+    }
76+
77+    QMap<long long, int>::Iterator i;
78+    long long pos, j=0; time_t time;
79+
80+    for (i = markmap.begin(); i != markmap.end(); ++i) {
81+       pos = i.key()/keyframe_dist;
82+       time = (double) i.key() / 29.97; /* #frames/NTSC framerate = #seconds */
83+       trackMap[time] = posMap[pos];
84+       j++;
85+    }
86+    return j+1;
87+}
88+
89 /** \fn ProgramInfo::ReactivateRecording(void)
90  *  \brief Asks the scheduler to restart this recording if possible.
91  */
92@@ -4857,3 +4912,4 @@
93 }
94 
95 /* vim: set expandtab tabstop=4 shiftwidth=4: */
96+
97Index: libs/libmythupnp/httprequest.cpp
98===================================================================
99--- libs/libmythupnp/httprequest.cpp    (revision 13462)
100+++ libs/libmythupnp/httprequest.cpp    (working copy)
101@@ -4,7 +4,7 @@
102 // Purpose - Http Request/Response
103 //                                                                           
104 // Created By  : David Blain                   Created On : Oct. 21, 2005
105-// Modified By :                                Modified On:                 
106+// Modified By : Rob Gingher                      Modified On:  Jun. 13, 2007               
107 //                                                                           
108 //////////////////////////////////////////////////////////////////////////////
109 
110@@ -41,26 +41,27 @@
111 
112 static MIMETypes g_MIMETypes[] =
113 {
114+    { "swf" , "application/futuresplash"   },
115+    { "pdf" , "application/pdf"            },
116+    { "xls" , "application/vnd.ms-excel"   },
117+    { "doc" , "application/vnd.ms-word"    },
118+    { "rm"  , "application/vnd.rn-realmedia" },
119+    { "zip" , "application/x-tar"          },
120+    { "gz"  , "application/x-tar"          },
121+    { "mid" , "audio/midi"                 },
122+    { "mp3" , "audio/mpeg"                 },
123+    { "m3u" , "audio/m3u"                  },
124+    { "wav" , "audio/wav"                  },
125     { "gif" , "image/gif"                  },
126     { "jpg" , "image/jpeg"                 },
127     { "png" , "image/png"                  },
128+    { "css" , "text/css"                   },
129     { "htm" , "text/html"                  },
130     { "html", "text/html"                  },
131     { "js"  , "text/html"                  },
132     { "txt" , "text/plain"                 },
133     { "xml" , "text/xml"                   },
134-    { "pdf" , "application/pdf"            },
135     { "avi" , "video/avi"                  },
136-    { "css" , "text/css"                   },
137-    { "swf" , "application/futuresplash"   },
138-    { "xls" , "application/vnd.ms-excel"   },
139-    { "doc" , "application/vnd.ms-word"    },
140-    { "mid" , "audio/midi"                 },
141-    { "mp3" , "audio/mpeg"                 },
142-    { "rm"  , "application/vnd.rn-realmedia" },
143-    { "wav" , "audio/wav"                  },
144-    { "zip" , "application/x-tar"          },
145-    { "gz"  , "application/x-tar"          },
146     { "mpg" , "video/mpeg"                 },
147     { "mpeg", "video/mpeg"                 },
148     { "vob",  "video/mpeg"                 },
149@@ -178,6 +179,8 @@
150                         .arg( nSize );
151     sHeader += "\r\n";
152 
153+//        VERBOSE(VB_UPNP, QString("HTTPRequest::BuildHeader : sHeader : %1 : ").arg(sHeader));
154+
155     return sHeader;
156 }
157 
158@@ -213,9 +216,9 @@
159             break;
160     }
161 
162-    // VERBOSE(VB_UPNP,QString("HTTPRequest::SendResponse(xml/html) :%1 -> %2:")
163-    //                    .arg(GetResponseStatus())
164-    //                    .arg(GetPeerAddress()));
165+//     VERBOSE(VB_UPNP,QString("HTTPRequest::SendResponse(xml/html) :%1 -> %2:")
166+//                        .arg(GetResponseStatus())
167+//                        .arg(GetPeerAddress()));
168 
169     // ----------------------------------------------------------------------
170     // Make it so the header is sent with the data
171@@ -239,8 +242,7 @@
172 
173     if (( m_eType != RequestTypeHead ) && ( m_aBuffer.size() > 0 ))
174     {
175-        //VERBOSE(VB_UPNP,QString("HTTPRequest::SendResponse : DATA : %1 : ")
176-        //                .arg(m_aBuffer.data()));
177+//        VERBOSE(VB_UPNP,QString("HTTPRequest::SendResponse : DATA : %1 : ").arg(m_aBuffer.data()));
178         
179         nBytes += WriteBlockDirect( m_aBuffer.data(), m_aBuffer.size() );
180     }
181@@ -271,16 +273,6 @@
182     m_eResponseType     = ResponseTypeOther;
183     m_sResponseTypeText = "text/plain";
184 
185-    /*
186-        Dump request header
187-    for ( QStringMap::iterator it  = m_mapHeaders.begin();
188-                               it != m_mapHeaders.end();
189-                             ++it )
190-    { 
191-        cout << it.key() << ": " << it.data() << endl;
192-    }
193-    */
194-
195     // ----------------------------------------------------------------------
196     // Make it so the header is sent with the data
197     // ----------------------------------------------------------------------
198@@ -297,7 +289,7 @@
199         m_sResponseTypeText = GetMimeType( info.extension( FALSE ).lower() );
200 
201         // ------------------------------------------------------------------
202-        // Get File size
203+        // Get File size - set llEnd to whole file
204         // ------------------------------------------------------------------
205 
206         struct stat st;
207@@ -306,42 +298,51 @@
208             llSize = llEnd = st.st_size;
209 
210         m_nResponseStatus = 200;
211+        QString sUserAgent = GetHeaderValue( "User-Agent", "");
212 
213-        // ------------------------------------------------------------------
214-        // The Content-Range header is apparently a problem for the
215-        // AVeL LinkPlayer2 and probably other hardware players with
216-        // Syabas firmware.
217-        //
218-        // -=>TODO: Need conformation
219-        // ------------------------------------------------------------------
220+        // --------------------------------------------------------------
221+        // Process bytes parameter for start and end offsets of track
222+        // --------------------------------------------------------------
223 
224+        QString sBytes = m_mapParams[ "bytes"];
225+        if(ParseRange( sBytes, &llStart, &llEnd )) llSize = llEnd - llStart + 1;
226+
227+        // --------------------------------------------------------------
228+        // Process Range header for ff/rew -- Offset from start of track
229+        // --------------------------------------------------------------
230+
231+        long long   llOffset = 0, llDummy  = 0;
232         bool    bRange     = false;
233-        QString sUserAgent = GetHeaderValue( "User-Agent", "");
234 
235-        if ( sUserAgent.contains( "Syabas", false ) == 0 )
236-        {
237-            // --------------------------------------------------------------
238-            // Process any Range Header
239-            // --------------------------------------------------------------
240+        QString sRange = GetHeaderValue( "range", "" );
241+        bRange = ParseRange ( sRange, &llOffset, &llDummy );
242+
243+//        VERBOSE(VB_UPNP,QString("HTTPRequest::SendResponseFile : Start/End %1-%2 Offset/Dummy %3-%4 bRange: %5")
244+//                                     .arg(llStart)
245+//                                     .arg(llEnd)
246+//                                     .arg(llOffset)
247+//                                     .arg(llDummy)
248+//                                     .arg(bRange));
249 
250-            QString sRange = GetHeaderValue( "range", "" );
251+        //
252+        //   Did user hit ff or rewind?  Is there a different start pos.?
253+        //
254 
255-            if (sRange.length() > 0)
256-            {
257-                if ( bRange = ParseRange( sRange, llSize, &llStart, &llEnd ) )
258-                {
259-                    m_nResponseStatus = 206;
260-                    m_mapRespHeaders[ "Content-Range" ] = QString("bytes %1-%2/%3")
261-                                                                  .arg( llStart )
262-                                                                  .arg( llEnd   )
263-                                                                  .arg( llSize  );
264-                    llSize = (llEnd - llStart) + 1;
265-                }
266-            }
267-        }
268-       
269-        // DSM-?20 specific response headers
270+        if ( bRange ) { /* process for partial content */
271+              m_nResponseStatus = 206;
272+              llDummy += llStart;
273+               llStart += llOffset;
274+              if( llDummy > llStart && llDummy << llEnd) llEnd = llDummy;
275+              llSize = llEnd - llStart + 1;
276+        }
277+       
278 
279+        m_mapRespHeaders[ "Content-Range" ] = QString("bytes %1-%2/%3")
280+                                                      .arg( llStart )
281+                                                      .arg( llEnd   )
282+                                                      .arg( llSize  );
283+     
284+        // DSM-?20 specific response headers
285         if (bRange == false)
286             m_mapRespHeaders[ "User-Agent"    ] = "redsonic";
287 
288@@ -350,7 +351,7 @@
289         // ------------------------------------------------------------------
290 
291     }
292-    else
293+    else /* file does not exist */
294         m_nResponseStatus = 404;
295 
296     // -=>TODO: Should set "Content-Length: *" if file is still recording
297@@ -405,6 +406,95 @@
298 }
299 
300 /////////////////////////////////////////////////////////////////////////////
301+// Parse Range
302+// returns: true if a non-zero start or end range is provided
303+/////////////////////////////////////////////////////////////////////////////
304+
305+bool HTTPRequest::ParseRange( QString sRange,
306+                              long long *pllStart,
307+                              long long *pllEnd   )
308+{
309+    // ----------------------------------------------------------------------       
310+    // -=>TODO: Only handle 1 range at this time... should make work with full spec.
311+    // ----------------------------------------------------------------------       
312+
313+    if (sRange.length() == 0)
314+        return false;
315+
316+    // ----------------------------------------------------------------------       
317+    // remove any "bytes="
318+    // ----------------------------------------------------------------------       
319+
320+    int nIdx = sRange.find( QRegExp( "(\\d|\\-)") );
321+
322+    if (nIdx < 0)
323+        return false;
324+
325+    if (nIdx > 0)
326+        sRange.remove( 0, nIdx );
327+
328+    // ----------------------------------------------------------------------       
329+    // Split multiple ranges
330+    // ----------------------------------------------------------------------       
331+
332+    QStringList ranges = QStringList::split( ",", sRange );
333+
334+    if (ranges.count() == 0)
335+        return false;
336+
337+    // ----------------------------------------------------------------------       
338+    // Split first range into its components
339+    // ----------------------------------------------------------------------       
340+
341+    QStringList parts = QStringList::split( "-", ranges[0], true );
342+
343+    if (parts.count() != 2)
344+        return false;
345+
346+    if (parts[0].isNull() && parts[1].isNull())
347+        return false;
348+
349+    // ----------------------------------------------------------------------       
350+    //
351+    // ----------------------------------------------------------------------       
352+
353+    if (parts[0].isNull())
354+    {
355+        // ------------------------------------------------------------------
356+        // Does it match "-####" ? return true
357+        // ------------------------------------------------------------------
358+
359+        *pllEnd   = strtoll( parts[1], NULL, 10 );
360+    }
361+    else if (parts[1].isNull())
362+    {
363+        // ------------------------------------------------------------------
364+        // Does it match "####-"? if 0-, return false, else return true
365+        // ------------------------------------------------------------------
366+
367+        *pllStart = strtoll( parts[0], NULL, 10 );
368+
369+        if (*pllStart == 0) return false;
370+    }
371+    else
372+    {
373+        // ------------------------------------------------------------------
374+        // Must be  "####-####" return false if invalid
375+        // ------------------------------------------------------------------
376+
377+        *pllStart = strtoll( parts[0], NULL, 10 );
378+        *pllEnd   = strtoll( parts[1], NULL, 10 );
379+
380+        if (*pllStart > *pllEnd)
381+            return false;
382+    }
383+
384+    //cout << getSocketHandle() << "Range Requested " << *pllStart << " - " << *pllEnd << endl;
385+
386+    return true;
387+}
388+
389+/////////////////////////////////////////////////////////////////////////////
390 //
391 /////////////////////////////////////////////////////////////////////////////
392 
393@@ -491,6 +581,9 @@
394     }
395     else
396         m_response << "</" << m_sMethod << "Response>\r\n";
397+
398+//    VERBOSE(VB_UPNP, QString("HTTPRequest::FormatActionResponse : m_aBuffer: %1" ).arg(m_aBuffer));
399+
400 }
401 
402 /////////////////////////////////////////////////////////////////////////////
403@@ -715,7 +808,7 @@
404 
405     try
406     {
407-        // Read first line to determin requestType
408+        // Read first line to determine requestType
409 
410         QString sRequestLine = ReadLine( 2000 );
411 
412@@ -896,101 +989,6 @@
413 //
414 /////////////////////////////////////////////////////////////////////////////
415 
416-bool HTTPRequest::ParseRange( QString sRange,
417-                              long long   llSize,
418-                              long long *pllStart,
419-                              long long *pllEnd   )
420-{
421-    // ----------------------------------------------------------------------       
422-    // -=>TODO: Only handle 1 range at this time... should make work with full spec.
423-    // ----------------------------------------------------------------------       
424-
425-    if (sRange.length() == 0)
426-        return false;
427-
428-    // ----------------------------------------------------------------------       
429-    // remove any "bytes="
430-    // ----------------------------------------------------------------------       
431-
432-    int nIdx = sRange.find( QRegExp( "(\\d|\\-)") );
433-
434-    if (nIdx < 0)
435-        return false;
436-
437-    if (nIdx > 0)
438-        sRange.remove( 0, nIdx );
439-
440-    // ----------------------------------------------------------------------       
441-    // Split multiple ranges
442-    // ----------------------------------------------------------------------       
443-
444-    QStringList ranges = QStringList::split( ",", sRange );
445-
446-    if (ranges.count() == 0)
447-        return false;
448-
449-    // ----------------------------------------------------------------------       
450-    // Split first range into its components
451-    // ----------------------------------------------------------------------       
452-
453-    QStringList parts = QStringList::split( "-", ranges[0], true );
454-
455-    if (parts.count() != 2)
456-        return false;
457-
458-    if (parts[0].isNull() && parts[1].isNull())
459-        return false;
460-
461-    // ----------------------------------------------------------------------       
462-    //
463-    // ----------------------------------------------------------------------       
464-
465-    if (parts[0].isNull())
466-    {
467-        // ------------------------------------------------------------------
468-        // Does it match "-####"
469-        // ------------------------------------------------------------------
470-
471-        long long llValue = strtoll( parts[1], NULL, 10 );
472-
473-        *pllStart = llSize - llValue;
474-        *pllEnd   = llSize - 1;
475-    }
476-    else if (parts[1].isNull())
477-    {
478-        // ------------------------------------------------------------------
479-        // Does it match "####-"
480-        // ------------------------------------------------------------------
481-
482-        *pllStart = strtoll( parts[0], NULL, 10 );
483-
484-        if (*pllStart == 0)
485-            return false;
486-
487-        *pllEnd   = llSize - 1;
488-    }
489-    else
490-    {
491-        // ------------------------------------------------------------------
492-        // Must be  "####-####"
493-        // ------------------------------------------------------------------
494-
495-        *pllStart = strtoll( parts[0], NULL, 10 );
496-        *pllEnd   = strtoll( parts[1], NULL, 10 );
497-
498-        if (*pllStart > *pllEnd)
499-            return false;
500-    }
501-
502-    //cout << getSocketHandle() << "Range Requested " << *pllStart << " - " << *pllEnd << endl;
503-
504-    return true;
505-}
506-
507-/////////////////////////////////////////////////////////////////////////////
508-//
509-/////////////////////////////////////////////////////////////////////////////
510-
511 void HTTPRequest::ExtractMethodFromURL()
512 {
513     QStringList sList = QStringList::split( "/", m_sBaseUrl, false );
514Index: libs/libmythupnp/httprequest.h
515===================================================================
516--- libs/libmythupnp/httprequest.h      (revision 13462)
517+++ libs/libmythupnp/httprequest.h      (working copy)
518@@ -147,7 +147,6 @@
519         QString         GetAdditionalHeaders( void );
520 
521         bool            ParseRange          ( QString sRange,
522-                                              long long   llSize,
523                                               long long *pllStart,
524                                               long long *pllEnd   );
525 
526Index: programs/mythbackend/mythxml.cpp
527===================================================================
528--- programs/mythbackend/mythxml.cpp    (revision 13462)
529+++ programs/mythbackend/mythxml.cpp    (working copy)
530@@ -4,8 +4,10 @@
531 // Purpose - Html & XML status HttpServerExtension
532 //                                                                           
533 // Created By  : David Blain                    Created On : Oct. 24, 2005
534-// Modified By :                                Modified On:                 
535-//                                                                           
536+//
537+// Modified By : Robert Gingher                 Modified On: Jun. 5, 2007                 
538+//   * added method genm3u for commercial break tracks
539+//                                                                       
540 //////////////////////////////////////////////////////////////////////////////
541 
542 #include "mythxml.h"
543@@ -85,6 +87,7 @@
544     if (sURI == "GetExpiring"          ) return MXML_GetExpiring;
545     if (sURI == "GetPreviewImage"      ) return MXML_GetPreviewImage;
546     if (sURI == "GetRecording"         ) return MXML_GetRecording;
547+    if (sURI == "GenM3u"               ) return MXML_GenM3u;
548     if (sURI == "GetVideo"             ) return MXML_GetVideo;
549     if (sURI == "GetMusic"             ) return MXML_GetMusic;
550     if (sURI == "GetConnectionInfo"    ) return MXML_GetConnectionInfo;
551@@ -123,6 +126,7 @@
552                 case MXML_GetPreviewImage      : GetPreviewImage( pRequest ); return true;
553 
554                 case MXML_GetRecording         : GetRecording   ( pThread, pRequest ); return true;
555+                case MXML_GenM3u               : GenM3u         ( pRequest ); return true;
556                 case MXML_GetMusic             : GetMusic       ( pThread, pRequest ); return true;
557                 case MXML_GetVideo             : GetVideo       ( pThread, pRequest ); return true;
558 
559@@ -970,7 +974,6 @@
560         // We only handle requests for local resources   
561 
562         delete pInfo;
563-
564         return;     
565     }
566 
567@@ -1136,6 +1139,8 @@
568 }
569 
570 /////////////////////////////////////////////////////////////////////////////
571+//
572+// GetRecording
573 //                 
574 /////////////////////////////////////////////////////////////////////////////
575 
576@@ -1149,7 +1154,7 @@
577     pRequest->m_nResponseStatus = 404;
578 
579     QString sChanId   = pRequest->m_mapParams[ "ChanId"    ];
580-    QString sStartTime= pRequest->m_mapParams[ "StartTime" ];
581+    QString sStartTime = pRequest->m_mapParams[ "StartTime" ];
582 
583     if (sStartTime.length() == 0)
584     {
585@@ -1157,6 +1162,44 @@
586         return;
587     }
588 
589+// --------------------------------------------------------------
590+// Process track parameter for start and end offsets of track
591+// and set bytes parameter
592+// --------------------------------------------------------------
593+
594+    int numtracks, trackno = pRequest->m_mapParams[ "track" ].toInt();
595+    pos_time_map_t trackMap;
596+    QString sStart, sRange;
597+    QMap<long long, time_t>::Iterator i;
598+    int j=0;
599+
600+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( sChanId, sStartTime );
601+
602+    numtracks=pInfo->GetTrackMap( sChanId, sStartTime, trackMap );
603+    if (numtracks > 0 && trackno > 0 && trackno <= numtracks ) { /* is there a valid track parameter? */
604+       sStart = "0-";
605+       i = trackMap.begin();
606+       if (trackno > 1) {
607+         for(j=1 ; j<=trackno-2; j++) ++i;
608+         sStart = QString("%1-")
609+                       .arg(i.data());
610+         i++;
611+       }
612+       sRange = sStart;
613+       if (trackno < numtracks ) sRange = QString("%1%2")
614+                       .arg(sStart)
615+                       .arg(i.data());
616+       pRequest->m_mapParams[ "bytes" ] = sRange;
617+    }
618+
619+    delete pInfo;
620+
621+/*            VERBOSE( VB_UPNP, QString( "MythXML::GetRecording - trackno = %1; range = %2; bytes = %3" )
622+                                 .arg( trackno )
623+                                 .arg( sRange)
624+                                       .arg( pRequest->m_mapParams["bytes"]));
625+*/
626+
627     // ----------------------------------------------------------------------
628     // DSM-320 & DSM-520 Special File Request for Index file of MPEG
629     //
630@@ -1230,7 +1273,6 @@
631                                  .arg( pInfo->hostname ));
632 
633             delete pInfo;
634-
635             return;     
636         }
637 
638@@ -1267,8 +1309,104 @@
639 
640 /////////////////////////////////////////////////////////////////////////////
641 //
642+// GenM3u
643+//                 
644 /////////////////////////////////////////////////////////////////////////////
645 
646+void MythXML::GenM3u ( HTTPRequest      *pRequest )
647+{
648+
649+    pRequest->m_eResponseType   = ResponseTypeHTML;
650+    pRequest->m_mapRespHeaders[ "Cache-Control" ] = "no-cache=\"Ext\", max-age = 5000";
651+    pRequest->m_nResponseStatus = 404;
652+
653+    QString sStartTime = pRequest->m_mapParams[ "StartTime" ];
654+    QString sChanId    = pRequest->m_mapParams[ "ChanId"    ];
655+
656+    QDateTime dtStart = QDateTime::fromString( sStartTime, Qt::ISODate );
657+
658+    if (!dtStart.isValid())
659+    {
660+        VERBOSE( VB_UPNP, "MythXML::GenM3u - StartTime missing.");
661+        return;
662+    }
663+   
664+    QString sHostIP = gContext->GetSetting( "BackendServerIP", "localhost" );
665+    QString sHostName = gContext->GetHostName();
666+    QString sPort     = gContext->GetSettingOnHost( "BackendStatusPort", sHostName);
667+    QString sRecordingUrl  = QString( "http://%1:%2/Myth/GetRecording?ChanId=%3&StartTime=%4" )
668+                                   .arg( sHostIP )
669+                                   .arg( sPort )
670+                                   .arg( sChanId )
671+                                   .arg( sStartTime );
672+
673+    pos_time_map_t trackMap;
674+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( sChanId, dtStart );
675+    int num=pInfo->GetTrackMap( sChanId, sStartTime, trackMap);
676+    delete pInfo;
677+
678+    if( num == 0) return;
679+
680+    for (int i = 1; i <= num; ++i)
681+       pRequest->m_response << QString("%1&track=%2\r\n" )
682+                                 .arg( sRecordingUrl )
683+                                 .arg( i );
684+
685+    pRequest->m_eResponseType     = ResponseTypeOther;
686+    pRequest->m_sFileName = "GenM3u.m3u";
687+    pRequest->m_sResponseTypeText = pRequest->GetMimeType( "m3u" );
688+    pRequest->m_nResponseStatus   = 200;
689+
690+/* If we need to create a file, modify this code
691+
692+        sFileName = QString( "%1.%2x%3.png" )
693+                                   .arg( pRequest->m_sFileName )
694+                                   .arg( nWidth    )
695+                                   .arg( nHeight   );
696+
697+    // ----------------------------------------------------------------------
698+    // check to see if image is already created.
699+    // ----------------------------------------------------------------------
700+
701+    if (QFile::exists( sFileName ))
702+    {
703+        pRequest->m_eResponseType   = ResponseTypeFile;
704+        pRequest->m_nResponseStatus = 200;
705+        pRequest->m_sFileName = sFileName;
706+        return;
707+    }
708+
709+    float fAspect = 0.0;
710+
711+    QImage *pImage = new QImage(pRequest->m_sFileName);
712+
713+    if (!pImage)
714+        return;
715+
716+    if (fAspect <= 0)
717+           fAspect = (float)(pImage->width()) / pImage->height();
718+
719+    if ( nWidth == 0 )
720+        nWidth = (int)rint(nHeight * fAspect);
721+
722+    if ( nHeight == 0 )
723+        nHeight = (int)rint(nWidth / fAspect);
724+
725+    QImage img = pImage->smoothScale( nWidth, nHeight);
726+
727+    img.save( sFileName.ascii(), "PNG" );
728+
729+    delete pImage;
730+
731+    pRequest->m_sFileName = sFileName;
732+*/
733+}
734+
735+
736+/////////////////////////////////////////////////////////////////////////////
737+//
738+/////////////////////////////////////////////////////////////////////////////
739+
740 void MythXML::GetMusic( HttpWorkerThread *pThread,
741                         HTTPRequest      *pRequest )
742 {
743@@ -1618,3 +1756,4 @@
744 }
745 
746 // vim:set shiftwidth=4 tabstop=4 expandtab:
747+
748Index: programs/mythbackend/mediaserver.cpp
749===================================================================
750--- programs/mythbackend/mediaserver.cpp        (revision 13462)
751+++ programs/mythbackend/mediaserver.cpp        (working copy)
752@@ -125,6 +125,7 @@
753                                        "http-get:*:image/png:*,"
754                                        "http-get:*:video/avi:*,"
755                                        "http-get:*:audio/mpeg:*,"
756+                                       "http-get:*:audio/m3u:*,"
757                                        "http-get:*:audio/wav:*,"
758                                        "http-get:*:video/mpeg:*,"
759                                        "http-get:*:video/nupplevideo:*,"
760Index: programs/mythbackend/upnpcdstv.cpp
761===================================================================
762--- programs/mythbackend/upnpcdstv.cpp  (revision 13462)
763+++ programs/mythbackend/upnpcdstv.cpp  (working copy)
764@@ -4,7 +4,8 @@
765 // Purpose - uPnp Content Directory Extention for Recorded TV 
766 //                                                                           
767 // Created By  : David Blain                    Created On : Jan. 24, 2005
768-// Modified By :                                Modified On:                 
769+// Modified By : Robert Gingher                 Modified On: Jun. 11, 2007
770+// *** added commercial free playlist ***                 
771 //                                                                           
772 //////////////////////////////////////////////////////////////////////////////
773 
774@@ -19,18 +20,25 @@
775 
776 /*
777    Recordings                              RecTv
778-    - All Programs                         RecTv/All
779-      + <recording 1>                      RecTv/All/item?ChanId=1004&StartTime=2006-04-06T20:00:00
780+    - All Programs                         RecTv/0
781+      + <recording 1>                      RecTv/0/item?ChanId=1004&StartTime=2006-04-06T20:00:00
782       + <recording 2>
783       + <recording 3>
784-    - By Title                             RecTv/title
785-      - <title 1>                          RecTv/title/key=Stargate SG-1
786-        + <recording 1>                    RecTv/title/key=Stargate SG-1/item?ChanId=1004&StartTime=2006-04-06T20:00:00
787+
788+    - Commercial Free                    RecTv/1
789+      - <recording 1>                      RecTv/1/key=1004@2006-04-06T20:00:00
790+        + <track 1>                              RecTv/1/key=1004@2006-04-06T20:00:00/item?ChanId=1004&StartTime=2006-04-06T20:00:00&track=1
791+        + <track 2>                        RecTv/1/key=1004@2006-04-06T20:00:00/item?ChanId=1004&StartTime=2006-04-06T20:00:00&track=2
792+
793+    - By Title                             RecTv/2
794+      - <title 1>                          RecTv/2/key=Stargate SG-1
795+        + <recording 1>                    RecTv/2/key=Stargate SG-1/item?ChanId=1004&StartTime=2006-04-06T20:00:00
796         + <recording 2>
797+
798     - By Genre
799     - By Date
800     - By Channel
801-    - By Group
802+    - By Recording Group
803 */
804 
805 
806@@ -39,13 +47,25 @@
807     {   "All Recordings",
808         "*",
809         "SELECT 0 as key, "
810-          "CONCAT( title, ': ', subtitle) as name, "
811+          "CONCAT( DATE_FORMAT(starttime, '%Y/%m/%d'),' ',title,': ', subtitle) as name, "
812           "1 as children "
813             "FROM recorded "
814             "%1 "
815-            "ORDER BY starttime DESC",
816+            "ORDER BY name DESC",
817         "" },
818 
819+    {  "Commercial Free",
820+       "CONCAT(chanid, '@', starttime)",
821+       "SELECT CONCAT(chanid, '@', starttime) as id, "
822+         "CONCAT( DATE_FORMAT(starttime, '%Y/%m/%d'),' ',title,': ', subtitle) as name, "
823+         "count( commflagged ) as children "
824+           "FROM recorded "
825+               "WHERE commflagged = 1 "
826+           "%1 "
827+           "GROUP BY name "
828+           "ORDER BY name DESC",
829+         "AND id = :KEY" },
830+
831     {   "By Title",
832         "title",
833         "SELECT title as id, "
834@@ -53,9 +73,9 @@
835           "count( title ) as children "
836             "FROM recorded "
837             "%1 "
838-            "GROUP BY title "
839-            "ORDER BY title",
840-        "WHERE title=:KEY" },
841+            "GROUP BY name "
842+            "ORDER BY name",
843+        "WHERE id = :KEY" },
844 
845     {   "By Genre",
846         "category",
847@@ -64,32 +84,32 @@
848           "count( category ) as children "
849             "FROM recorded "
850             "%1 "
851-            "GROUP BY category "
852-            "ORDER BY category",
853-        "WHERE category=:KEY" },
854+            "GROUP BY name "
855+            "ORDER BY name",
856+        "WHERE id= :KEY" },
857 
858     {   "By Date",
859         "DATE_FORMAT(starttime, '%Y-%m-%d')",
860         "SELECT  DATE_FORMAT(starttime, '%Y-%m-%d') as id, "
861-          "DATE_FORMAT(starttime, '%Y-%m-%d %W') as name, "
862+          "CONCAT('Recorded on ',DATE_FORMAT(starttime, '%m/%d/%Y')) as name, "
863           "count( DATE_FORMAT(starttime, '%Y-%m-%d %W') ) as children "
864             "FROM recorded "
865             "%1 "
866             "GROUP BY name "
867             "ORDER BY starttime DESC",
868-        "WHERE DATE_FORMAT(starttime, '%Y-%m-%d') =:KEY" },
869+        "WHERE id = :KEY" },
870 
871     {   "By Channel",
872         "chanid",
873         "SELECT channel.chanid as id, "
874-          "CONCAT(channel.channum, ' ', channel.callsign) as name, "
875+          "CONCAT('Channel ',channel.channum, ' ', channel.callsign) as name, "
876           "count( channum ) as children "
877             "FROM channel "
878                 "INNER JOIN recorded ON channel.chanid = recorded.chanid "
879             "%1 "
880             "GROUP BY name "
881-            "ORDER BY channel.chanid",
882-        "WHERE channel.chanid=:KEY" },
883+            "ORDER BY id",
884+        "WHERE id = :KEY" },
885 
886 
887     {   "By Group",
888@@ -98,9 +118,9 @@
889           "recgroup as name, count( recgroup ) as children "
890             "FROM recorded "
891             "%1 "
892-            "GROUP BY recgroup "
893-            "ORDER BY recgroup",
894-        "WHERE recgroup=:KEY" }
895+            "GROUP BY name "
896+            "ORDER BY name",
897+        "WHERE id = :KEY" }
898 };
899 
900 int UPnpCDSTv::g_nRootCount = sizeof( g_RootNodes ) / sizeof( UPnpCDSRootInfo );
901@@ -168,6 +188,8 @@
902 
903 /////////////////////////////////////////////////////////////////////////////
904 //
905+// AddItem()
906+//
907 /////////////////////////////////////////////////////////////////////////////
908 
909 void UPnpCDSTv::AddItem( const QString           &sObjectId,
910@@ -175,7 +197,7 @@
911                          bool                     bAddRef,
912                          MSqlQuery               &query )
913 {
914-    int            nChanid      = query.value( 0).toInt();
915+    int             nChanId      = query.value( 0).toInt();
916     QDateTime      dtStartTime  = query.value( 1).toDateTime();
917     QDateTime      dtEndTime    = query.value( 2).toDateTime();
918     QString        sTitle       = query.value( 3).toString();
919@@ -186,7 +208,6 @@
920     QString        sRecGroup    = query.value( 8).toString();
921     long long      nFileSize    = stringToLongLong( query.value( 9).toString() );
922     QString        sBaseName    = query.value(10).toString();
923-
924     QDateTime      dtProgStart  = query.value(11).toDateTime();
925     QDateTime      dtProgEnd    = query.value(12).toDateTime();
926 
927@@ -204,110 +225,138 @@
928     // Build Support Strings
929     // ----------------------------------------------------------------------
930 
931-    QString sName      = sTitle + ": " + sSubtitle;
932+    QStringList idPath = QStringList::split( "/", sObjectId.section('=',0,0) );
933+    bool commflag = (idPath[1] == "1");
934 
935     QString sURIBase   = QString( "http://%1:%2/Myth/" )
936                             .arg( m_mapBackendIp  [ sHostName ] )
937                             .arg( m_mapBackendPort[ sHostName ] );
938 
939-    QString sURIParams = QString( "?ChanId=%1&amp;StartTime=%2" )
940-                            .arg( nChanid )
941+    QString sName      = sTitle + ": " + sSubtitle;
942+
943+    QString sURIParams = QString( "?ChanId=%1&StartTime=%2" )
944+                            .arg( nChanId )
945                             .arg( dtStartTime.toString(Qt::ISODate));
946 
947-    QString sId        = QString( "%1/item%2")
948+    QString sStartTime = query.value(1).toString();
949+    pos_time_map_t trackMap;
950+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( QString("%1").arg(nChanId), sStartTime );
951+    int num=0;
952+    time_t uiStart, uiEnd=0;
953+    QMap<long long, time_t>::Iterator i;
954+    long long llSize, llStartPos, llEndPos=0;
955+
956+    if (pInfo != NULL && commflag) {
957+       num=pInfo->GetTrackMap( QString("%1").arg(nChanId), sStartTime, trackMap);
958+       i = trackMap.begin();
959+       delete pInfo;
960+       pResults->m_nTotalMatches = num;
961+    }
962+
963+    if (!commflag) num = 1;
964+
965+    if( num == 0) return;
966+
967+    for (int trackno = 1; trackno <= num; ++trackno )
968+    {
969+       QString sTrackParam = QString("&track=%1").arg(trackno);
970+       QString sTrackName = QString(" track %1").arg(trackno);
971+
972+       if ( !commflag ) {
973+         sTrackParam = "";
974+         sTrackName = "";
975+       }
976+
977+       QString sId        = QString( "%1/item%2%3")
978                             .arg( sObjectId )
979-                            .arg( sURIParams );
980+                            .arg( sURIParams )
981+                               .arg( sTrackParam );
982 
983-    CDSObject *pItem   = CDSObject::CreateVideoItem( sId,
984-                                                     sName,
985-                                                     sObjectId );
986-    pItem->m_bRestricted  = false;
987-    pItem->m_bSearchable  = true;
988-    pItem->m_sWriteStatus = "WRITABLE";
989+       CDSObject *pItem   = CDSObject::CreateVideoItem( sId, QString("%1%2").arg(sName).arg(sTrackName), sObjectId);
990+   
991+       pItem->m_bRestricted  = false;
992+       pItem->m_bSearchable  = true;
993+       pItem->m_sWriteStatus = "WRITABLE";
994 
995-    if ( bAddRef )
996-    {
997-        QString sRefId = QString( "%1/0/item%2")
998+       if ( bAddRef )
999+       {
1000+               QString sRefId = QString( "%1/0/item%2%3")
1001                             .arg( m_sExtensionId )
1002-                            .arg( sURIParams     );
1003+                            .arg( sURIParams     )
1004+                               .arg(sTrackParam);
1005 
1006-        pItem->SetPropValue( "refID", sRefId );
1007-    }
1008+               pItem->SetPropValue( "refID", sRefId );
1009+       }
1010 
1011-    pItem->SetPropValue( "genre"          , sCategory    );
1012-    pItem->SetPropValue( "longDescription", sDescription );
1013-    pItem->SetPropValue( "description"    , sSubtitle    );
1014+       pItem->SetPropValue( "genre"          , sCategory    );
1015+       pItem->SetPropValue( "longDescription", sDescription );
1016+       pItem->SetPropValue( "description"    , sSubtitle    );
1017 
1018-    //pItem->SetPropValue( "producer"       , );
1019-    //pItem->SetPropValue( "rating"         , );
1020-    //pItem->SetPropValue( "actor"          , );
1021-    //pItem->SetPropValue( "director"       , );
1022-    //pItem->SetPropValue( "publisher"      , );
1023-    //pItem->SetPropValue( "language"       , );
1024-    //pItem->SetPropValue( "relation"       , );
1025-    //pItem->SetPropValue( "region"         , );
1026-
1027     // ----------------------------------------------------------------------
1028     // Needed for Microsoft Media Player Compatibility
1029     // (Won't display correct Title without them)
1030     // ----------------------------------------------------------------------
1031 
1032-    pItem->SetPropValue( "creator"       , "[Unknown Author]" );
1033-    pItem->SetPropValue( "artist"        , "[Unknown Author]" );
1034-    pItem->SetPropValue( "album"         , "[Unknown Series]" );
1035-    pItem->SetPropValue( "actor"         , "[Unknown Author]" );
1036+       pItem->SetPropValue( "creator"       , "[Unknown Author]" );
1037+       pItem->SetPropValue( "artist"        , "[Unknown Author]" );
1038+       pItem->SetPropValue( "album"         , "[Unknown Series]" );
1039+       pItem->SetPropValue( "actor"         , "[Unknown Author]" );
1040 
1041-    pResults->Add( pItem );
1042+       pResults->Add( pItem );
1043 
1044-    // ----------------------------------------------------------------------
1045-    // Add Video Resource Element based on File extension (HTTP)
1046-    // ----------------------------------------------------------------------
1047+// ----------------------------------------------------------------------
1048+// Add Video Resource Element/Playlist based on File extension (HTTP)
1049+// ----------------------------------------------------------------------
1050     
1051-    QFileInfo fInfo( sBaseName );
1052+       QFileInfo fInfo( sBaseName );
1053+       QString sMimeType = HTTPRequest::GetMimeType( fInfo.extension( FALSE ));
1054+       QString sProtocol = QString( "http-get:*:%1:*" ).arg( sMimeType  );
1055+       QString sURI      = QString( "%1GetRecording%2%3").arg( sURIBase   )
1056+                                                               .arg( sURIParams )
1057+                                                               .arg(sTrackParam);
1058 
1059-    QString sMimeType = HTTPRequest::GetMimeType( fInfo.extension( FALSE ));
1060-    QString sProtocol = QString( "http-get:*:%1:*" ).arg( sMimeType  );
1061-    QString sURI      = QString( "%1GetRecording%2").arg( sURIBase   )
1062-                                                    .arg( sURIParams );
1063+       Resource *pRes = pItem->AddResource( sProtocol, sURI );
1064 
1065-    Resource *pRes = pItem->AddResource( sProtocol, sURI );
1066+       if ( !commflag ) {
1067+         uiStart = dtProgStart.toTime_t();
1068+         uiEnd   = dtProgEnd.toTime_t();
1069+         llSize = nFileSize;
1070+       } else {
1071+         uiStart = uiEnd;
1072+         llStartPos = llEndPos;
1073+         llEndPos = i.data();
1074+         uiEnd = i.key();
1075+         if (trackno == num) {
1076+               uiEnd = dtProgEnd.toTime_t() - dtProgStart.toTime_t();
1077+               llEndPos = nFileSize;
1078+         }
1079+         llSize = llEndPos - llStartPos;
1080+       }
1081 
1082-    uint uiStart = dtProgStart.toTime_t();
1083-    uint uiEnd   = dtProgEnd.toTime_t();
1084-    uint uiDur   = uiEnd - uiStart;
1085+/*      VERBOSE( VB_UPNP, QString( "UpnpCDSTv::AddItem() - Id %1 start = %2 end = %3 size = %4" )
1086+                                       .arg( sId )
1087+                                       .arg( uiStart )
1088+                                       .arg( uiEnd )
1089+                                       .arg( llSize ));
1090+*/
1091+       time_t uiDur = uiEnd - uiStart;
1092 
1093-
1094-    QString sDur = QString( "%1:%2:%3" )
1095+       QString sDur = QString( "%1:%2:%3" )
1096                     .arg( (uiDur / 3600) % 24, 2 )
1097                     .arg( (uiDur / 60) % 60  , 2 )
1098                     .arg(  uiDur % 60        , 2 );
1099 
1100-    pRes->AddAttribute( "duration"  , sDur      );
1101-    pRes->AddAttribute( "size"      , longLongToString( nFileSize) );
1102+       pRes->AddAttribute( "duration"  , sDur      );
1103+       pRes->AddAttribute( "size"      , longLongToString( llSize ) );
1104 
1105-/*
1106     // ----------------------------------------------------------------------
1107-    // Add Video Resource Element based on File extension (mythtv)
1108-    // ----------------------------------------------------------------------
1109-
1110-    sProtocol = QString( "myth:*:%1:*"     ).arg( sMimeType  );
1111-    sURI      = QString( "myth://%1/%2" )
1112-                   .arg( m_mapBackendIp  [ sHostName ] )
1113-                   .arg( sBaseName );
1114-
1115-    pRes = pItem->AddResource( sProtocol, sURI );
1116-
1117-    pRes->AddAttribute( "duration"  , sDur      );
1118-    pRes->AddAttribute( "size"      , longLongToString( nFileSize) );
1119-*/
1120-    // ----------------------------------------------------------------------
1121     // Add Thumbnail Resource
1122     // ----------------------------------------------------------------------
1123 
1124-    sURI = QString( "%1GetPreviewImage%2").arg( sURIBase   )
1125+       sURI = QString( "%1GetPreviewImage%2").arg( sURIBase   )
1126                                           .arg( sURIParams );
1127 
1128-    pItem->AddResource( "http-get:*:image/png:*" , sURI );
1129-
1130+       pItem->AddResource( "http-get:*:image/png:*" , sURI );
1131+       if(commflag) ++i;
1132+    } /* for */
1133 }
1134-
1135Index: programs/mythbackend/mythxml.h
1136===================================================================
1137--- programs/mythbackend/mythxml.h      (revision 13462)
1138+++ programs/mythbackend/mythxml.h      (working copy)
1139@@ -4,7 +4,7 @@
1140 // Purpose - Myth XML protocol HttpServerExtension
1141 //                                                                           
1142 // Created By  : David Blain                    Created On : Oct. 24, 2005
1143-// Modified By :                                Modified On:                 
1144+// Modified By : R. Gingher                     Modified On: Jun. 5, 2007                 
1145 //                                                                           
1146 //////////////////////////////////////////////////////////////////////////////
1147 
1148@@ -44,14 +44,15 @@
1149     MXML_GetPreviewImage        =  9,
1150 
1151     MXML_GetRecording           = 10,
1152-    MXML_GetMusic               = 11,
1153+    MXML_GenM3u                 = 11,
1154+    MXML_GetMusic               = 12,
1155 
1156-    MXML_GetExpiring            = 12,
1157-    MXML_GetProgramDetails      = 13,
1158-    MXML_GetVideo               = 14,
1159+    MXML_GetExpiring            = 13,
1160+    MXML_GetProgramDetails      = 14,
1161+    MXML_GetVideo               = 15,
1162 
1163-    MXML_GetConnectionInfo      = 15,
1164-    MXML_GetAlbumArt            = 16
1165+    MXML_GetConnectionInfo      = 16,
1166+    MXML_GetAlbumArt            = 17
1167 
1168 } MythXMLMethod;
1169 
1170@@ -114,14 +115,17 @@
1171         void    GetRecording   ( HttpWorkerThread *pThread,
1172                                  HTTPRequest      *pRequest );
1173 
1174+        void    GenM3u         ( HTTPRequest      *pRequest );
1175+
1176+
1177         void    GetMusic       ( HttpWorkerThread *pThread,
1178                                  HTTPRequest      *pRequest );
1179 
1180         void    GetVideo       ( HttpWorkerThread *pThread,
1181                                  HTTPRequest      *pRequest );
1182 
1183+        void    GetDeviceDesc  ( HTTPRequest *pRequest );
1184 
1185-        void    GetDeviceDesc  ( HTTPRequest *pRequest );
1186         void    GetFile        ( HTTPRequest *pRequest, QString sFileName );
1187 
1188     public:
1189@@ -130,6 +134,8 @@
1190 
1191         bool     ProcessRequest( HttpWorkerThread *pThread, HTTPRequest *pRequest );
1192 
1193+        int       TrackEndMap ( QString sChanId, QDateTime dtStart, frm_pos_map_t &trackMap );
1194+
1195         // Static methods shared with HttpStatus
1196 
1197         static void FillProgramInfo ( QDomDocument *pDoc,
1198Index: programs/mythbackend/upnpcdsmusic.cpp
1199===================================================================
1200--- programs/mythbackend/upnpcdsmusic.cpp       (revision 13462)
1201+++ programs/mythbackend/upnpcdsmusic.cpp       (working copy)
1202@@ -127,7 +127,7 @@
1203 //
1204 /////////////////////////////////////////////////////////////////////////////
1205 
1206-QString UPnpCDSMusic::GetTableName( QString sColumn )
1207+QString UPnpCDSMusic::GetTableName( QString /* sColumn */)
1208 {
1209     return "music_songs song";
1210 }