Ticket #3580: patchfile.3

File patchfile.3, 40.9 KB (added by robsbox@…, 17 years ago)

additional changes for track parameter and start of CDS modification

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