Ticket #3580: patchfile.5

File patchfile.5, 43.8 KB (added by robsbox@…, 17 years ago)

Final patch file fixing fast forwarding and implementing commercial skipping tracks.

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@@ -1136,6 +1140,8 @@
560 }
561 
562 /////////////////////////////////////////////////////////////////////////////
563+//
564+// GetRecording
565 //                 
566 /////////////////////////////////////////////////////////////////////////////
567 
568@@ -1149,7 +1155,7 @@
569     pRequest->m_nResponseStatus = 404;
570 
571     QString sChanId   = pRequest->m_mapParams[ "ChanId"    ];
572-    QString sStartTime= pRequest->m_mapParams[ "StartTime" ];
573+    QString sStartTime = pRequest->m_mapParams[ "StartTime" ];
574 
575     if (sStartTime.length() == 0)
576     {
577@@ -1157,6 +1163,44 @@
578         return;
579     }
580 
581+// --------------------------------------------------------------
582+// Process track parameter for start and end offsets of track
583+// and set bytes parameter
584+// --------------------------------------------------------------
585+
586+    int numtracks, trackno = pRequest->m_mapParams[ "track" ].toInt();
587+    pos_time_map_t trackMap;
588+    QString sStart, sRange;
589+    QMap<long long, time_t>::Iterator i;
590+    int j=0;
591+
592+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( sChanId, sStartTime );
593+
594+    numtracks=pInfo->GetTrackMap( sChanId, sStartTime, trackMap );
595+    if (numtracks > 0 && trackno > 0 && trackno <= numtracks ) { /* is there a valid track parameter? */
596+       sStart = "0-";
597+       i = trackMap.begin();
598+       if (trackno > 1) {
599+         for(j=1 ; j<=trackno-2; j++) ++i;
600+         sStart = QString("%1-")
601+                       .arg(i.data());
602+         i++;
603+       }
604+       sRange = sStart;
605+       if (trackno < numtracks ) sRange = QString("%1%2")
606+                       .arg(sStart)
607+                       .arg(i.data());
608+       pRequest->m_mapParams[ "bytes" ] = sRange;
609+    }
610+
611+    delete pInfo;
612+
613+/*            VERBOSE( VB_UPNP, QString( "MythXML::GetRecording - trackno = %1; range = %2; bytes = %3" )
614+                                 .arg( trackno )
615+                                 .arg( sRange)
616+                                       .arg( pRequest->m_mapParams["bytes"]));
617+*/
618+
619     // ----------------------------------------------------------------------
620     // DSM-320 & DSM-520 Special File Request for Index file of MPEG
621     //
622@@ -1267,8 +1311,103 @@
623 
624 /////////////////////////////////////////////////////////////////////////////
625 //
626+// GenM3u
627+//                 
628 /////////////////////////////////////////////////////////////////////////////
629 
630+void MythXML::GenM3u ( HTTPRequest      *pRequest )
631+{
632+
633+    pRequest->m_eResponseType   = ResponseTypeHTML;
634+    pRequest->m_mapRespHeaders[ "Cache-Control" ] = "no-cache=\"Ext\", max-age = 5000";
635+    pRequest->m_nResponseStatus = 404;
636+
637+    QString sStartTime = pRequest->m_mapParams[ "StartTime" ];
638+    QString sChanId    = pRequest->m_mapParams[ "ChanId"    ];
639+
640+    QDateTime dtStart = QDateTime::fromString( sStartTime, Qt::ISODate );
641+
642+    if (!dtStart.isValid())
643+    {
644+        VERBOSE( VB_UPNP, "MythXML::GenM3u - StartTime missing.");
645+        return;
646+    }
647+   
648+    QString sHostIP = gContext->GetSetting( "BackendServerIP", "localhost" );
649+    QString sHostName = gContext->GetHostName();
650+    QString sPort     = gContext->GetSettingOnHost( "BackendStatusPort", sHostName);
651+    QString sRecordingUrl  = QString( "http://%1:%2/Myth/GetRecording?ChanId=%3&StartTime=%4" )
652+                                   .arg( sHostIP )
653+                                   .arg( sPort )
654+                                   .arg( sChanId )
655+                                   .arg( sStartTime );
656+
657+    pos_time_map_t trackMap;
658+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( sChanId, dtStart );
659+    int num=pInfo->GetTrackMap( sChanId, sStartTime, trackMap);
660+
661+    if( num == 0) return;
662+
663+    for (int i = 1; i <= num; ++i)
664+       pRequest->m_response << QString("%1&track=%2\r\n" )
665+                                 .arg( sRecordingUrl )
666+                                 .arg( i );
667+
668+    pRequest->m_eResponseType     = ResponseTypeOther;
669+    pRequest->m_sFileName = "GenM3u.m3u";
670+    pRequest->m_sResponseTypeText = pRequest->GetMimeType( "m3u" );
671+    pRequest->m_nResponseStatus   = 200;
672+
673+/* If we need to create a file, modify this code
674+
675+        sFileName = QString( "%1.%2x%3.png" )
676+                                   .arg( pRequest->m_sFileName )
677+                                   .arg( nWidth    )
678+                                   .arg( nHeight   );
679+
680+    // ----------------------------------------------------------------------
681+    // check to see if image is already created.
682+    // ----------------------------------------------------------------------
683+
684+    if (QFile::exists( sFileName ))
685+    {
686+        pRequest->m_eResponseType   = ResponseTypeFile;
687+        pRequest->m_nResponseStatus = 200;
688+        pRequest->m_sFileName = sFileName;
689+        return;
690+    }
691+
692+    float fAspect = 0.0;
693+
694+    QImage *pImage = new QImage(pRequest->m_sFileName);
695+
696+    if (!pImage)
697+        return;
698+
699+    if (fAspect <= 0)
700+           fAspect = (float)(pImage->width()) / pImage->height();
701+
702+    if ( nWidth == 0 )
703+        nWidth = (int)rint(nHeight * fAspect);
704+
705+    if ( nHeight == 0 )
706+        nHeight = (int)rint(nWidth / fAspect);
707+
708+    QImage img = pImage->smoothScale( nWidth, nHeight);
709+
710+    img.save( sFileName.ascii(), "PNG" );
711+
712+    delete pImage;
713+
714+    pRequest->m_sFileName = sFileName;
715+*/
716+}
717+
718+
719+/////////////////////////////////////////////////////////////////////////////
720+//
721+/////////////////////////////////////////////////////////////////////////////
722+
723 void MythXML::GetMusic( HttpWorkerThread *pThread,
724                         HTTPRequest      *pRequest )
725 {
726@@ -1618,3 +1757,4 @@
727 }
728 
729 // vim:set shiftwidth=4 tabstop=4 expandtab:
730+
731Index: programs/mythbackend/mediaserver.cpp
732===================================================================
733--- programs/mythbackend/mediaserver.cpp        (revision 13462)
734+++ programs/mythbackend/mediaserver.cpp        (working copy)
735@@ -125,6 +125,7 @@
736                                        "http-get:*:image/png:*,"
737                                        "http-get:*:video/avi:*,"
738                                        "http-get:*:audio/mpeg:*,"
739+                                       "http-get:*:audio/m3u:*,"
740                                        "http-get:*:audio/wav:*,"
741                                        "http-get:*:video/mpeg:*,"
742                                        "http-get:*:video/nupplevideo:*,"
743Index: programs/mythbackend/upnpcdstv.cpp
744===================================================================
745--- programs/mythbackend/upnpcdstv.cpp  (revision 13462)
746+++ programs/mythbackend/upnpcdstv.cpp  (working copy)
747@@ -4,7 +4,8 @@
748 // Purpose - uPnp Content Directory Extention for Recorded TV 
749 //                                                                           
750 // Created By  : David Blain                    Created On : Jan. 24, 2005
751-// Modified By :                                Modified On:                 
752+// Modified By : Robert Gingher                 Modified On: Jun. 11, 2007
753+// *** added commercial free playlist ***                 
754 //                                                                           
755 //////////////////////////////////////////////////////////////////////////////
756 
757@@ -19,18 +20,25 @@
758 
759 /*
760    Recordings                              RecTv
761-    - All Programs                         RecTv/All
762-      + <recording 1>                      RecTv/All/item?ChanId=1004&StartTime=2006-04-06T20:00:00
763+    - All Programs                         RecTv/0
764+      + <recording 1>                      RecTv/0/item?ChanId=1004&StartTime=2006-04-06T20:00:00
765       + <recording 2>
766       + <recording 3>
767-    - By Title                             RecTv/title
768-      - <title 1>                          RecTv/title/key=Stargate SG-1
769-        + <recording 1>                    RecTv/title/key=Stargate SG-1/item?ChanId=1004&StartTime=2006-04-06T20:00:00
770+
771+    - Commercial Free                    RecTv/1
772+      - <recording 1>                      RecTv/1/key=1004@2006-04-06T20:00:00
773+        + <track 1>                              RecTv/1/key=1004@2006-04-06T20:00:00/item?ChanId=1004&StartTime=2006-04-06T20:00:00&track=1
774+        + <track 2>                        RecTv/1/key=1004@2006-04-06T20:00:00/item?ChanId=1004&StartTime=2006-04-06T20:00:00&track=2
775+
776+    - By Title                             RecTv/2
777+      - <title 1>                          RecTv/2/key=Stargate SG-1
778+        + <recording 1>                    RecTv/2/key=Stargate SG-1/item?ChanId=1004&StartTime=2006-04-06T20:00:00
779         + <recording 2>
780+
781     - By Genre
782     - By Date
783     - By Channel
784-    - By Group
785+    - By Recording Group
786 */
787 
788 
789@@ -39,13 +47,25 @@
790     {   "All Recordings",
791         "*",
792         "SELECT 0 as key, "
793-          "CONCAT( title, ': ', subtitle) as name, "
794+          "CONCAT( DATE_FORMAT(starttime, '%m/%d/%Y'),' ',title,': ', subtitle) as name, "
795           "1 as children "
796             "FROM recorded "
797             "%1 "
798             "ORDER BY starttime DESC",
799         "" },
800 
801+    {  "Commercial Free",
802+       "CONCAT(chanid, '@', starttime)",
803+       "SELECT CONCAT(chanid, '@', starttime) as id, "
804+         "CONCAT( DATE_FORMAT(starttime, '%Y/%m/%d'),' ',title,': ', subtitle) as name, "
805+         "count( commflagged ) as children "
806+           "FROM recorded "
807+               "WHERE commflagged = 1 "
808+           "%1 "
809+           "GROUP BY name "
810+           "ORDER BY name DESC",
811+         "AND id = :KEY" },
812+
813     {   "By Title",
814         "title",
815         "SELECT title as id, "
816@@ -53,9 +73,9 @@
817           "count( title ) as children "
818             "FROM recorded "
819             "%1 "
820-            "GROUP BY title "
821-            "ORDER BY title",
822-        "WHERE title=:KEY" },
823+            "GROUP BY name "
824+            "ORDER BY name",
825+        "WHERE id = :KEY" },
826 
827     {   "By Genre",
828         "category",
829@@ -64,32 +84,32 @@
830           "count( category ) as children "
831             "FROM recorded "
832             "%1 "
833-            "GROUP BY category "
834-            "ORDER BY category",
835-        "WHERE category=:KEY" },
836+            "GROUP BY name "
837+            "ORDER BY name",
838+        "WHERE id= :KEY" },
839 
840     {   "By Date",
841         "DATE_FORMAT(starttime, '%Y-%m-%d')",
842         "SELECT  DATE_FORMAT(starttime, '%Y-%m-%d') as id, "
843-          "DATE_FORMAT(starttime, '%Y-%m-%d %W') as name, "
844+          "CONCAT('Recorded on ',DATE_FORMAT(starttime, '%m/%d/%Y')) as name, "
845           "count( DATE_FORMAT(starttime, '%Y-%m-%d %W') ) as children "
846             "FROM recorded "
847             "%1 "
848             "GROUP BY name "
849             "ORDER BY starttime DESC",
850-        "WHERE DATE_FORMAT(starttime, '%Y-%m-%d') =:KEY" },
851+        "WHERE id = :KEY" },
852 
853     {   "By Channel",
854         "chanid",
855         "SELECT channel.chanid as id, "
856-          "CONCAT(channel.channum, ' ', channel.callsign) as name, "
857+          "CONCAT('Channel ',channel.channum, ' ', channel.callsign) as name, "
858           "count( channum ) as children "
859             "FROM channel "
860                 "INNER JOIN recorded ON channel.chanid = recorded.chanid "
861             "%1 "
862             "GROUP BY name "
863             "ORDER BY channel.chanid",
864-        "WHERE channel.chanid=:KEY" },
865+        "WHERE id = :KEY" },
866 
867 
868     {   "By Group",
869@@ -98,9 +118,9 @@
870           "recgroup as name, count( recgroup ) as children "
871             "FROM recorded "
872             "%1 "
873-            "GROUP BY recgroup "
874-            "ORDER BY recgroup",
875-        "WHERE recgroup=:KEY" }
876+            "GROUP BY name "
877+            "ORDER BY name",
878+        "WHERE id = :KEY" }
879 };
880 
881 int UPnpCDSTv::g_nRootCount = sizeof( g_RootNodes ) / sizeof( UPnpCDSRootInfo );
882@@ -168,6 +188,8 @@
883 
884 /////////////////////////////////////////////////////////////////////////////
885 //
886+// AddItem()
887+//
888 /////////////////////////////////////////////////////////////////////////////
889 
890 void UPnpCDSTv::AddItem( const QString           &sObjectId,
891@@ -175,7 +197,7 @@
892                          bool                     bAddRef,
893                          MSqlQuery               &query )
894 {
895-    int            nChanid      = query.value( 0).toInt();
896+    QString        sChanId      = query.value( 0).toString();
897     QDateTime      dtStartTime  = query.value( 1).toDateTime();
898     QDateTime      dtEndTime    = query.value( 2).toDateTime();
899     QString        sTitle       = query.value( 3).toString();
900@@ -186,7 +208,6 @@
901     QString        sRecGroup    = query.value( 8).toString();
902     long long      nFileSize    = stringToLongLong( query.value( 9).toString() );
903     QString        sBaseName    = query.value(10).toString();
904-
905     QDateTime      dtProgStart  = query.value(11).toDateTime();
906     QDateTime      dtProgEnd    = query.value(12).toDateTime();
907 
908@@ -204,110 +225,140 @@
909     // Build Support Strings
910     // ----------------------------------------------------------------------
911 
912-    QString sName      = sTitle + ": " + sSubtitle;
913+    QStringList idPath = QStringList::split( "/", sObjectId.section('=',0,0) );
914+    bool commflag = (idPath[0] == "RecTv" && idPath[1] == "1");
915 
916     QString sURIBase   = QString( "http://%1:%2/Myth/" )
917                             .arg( m_mapBackendIp  [ sHostName ] )
918                             .arg( m_mapBackendPort[ sHostName ] );
919 
920-    QString sURIParams = QString( "?ChanId=%1&amp;StartTime=%2" )
921-                            .arg( nChanid )
922+    QString sName      = sTitle + ": " + sSubtitle;
923+
924+    QString sURIParams = QString( "?ChanId=%1&StartTime=%2" )
925+                            .arg( sChanId )
926                             .arg( dtStartTime.toString(Qt::ISODate));
927 
928-    QString sId        = QString( "%1/item%2")
929+    QString sStartTime = query.value(1).toString();
930+    pos_time_map_t trackMap;
931+    ProgramInfo *pInfo = ProgramInfo::GetProgramFromRecorded( sChanId, sStartTime );
932+    int num=pInfo->GetTrackMap( sChanId, sStartTime, trackMap);
933+    time_t uiStart, uiEnd=0;
934+    QMap<long long, time_t>::Iterator i;
935+    long long llSize, llStartPos, llEndPos=0;
936+
937+    if( commflag ) pResults->m_nTotalMatches = num;
938+    else num = 1;
939+
940+    if( num == 0) return;
941+
942+    i = trackMap.begin();
943+
944+    for (int trackno = 1; trackno <= num; ++trackno )
945+    {
946+       QString sTrackParam = QString("&track=%1").arg(trackno);
947+       QString sTrackName = QString(" track %1").arg(trackno);
948+
949+       if ( !commflag ) {
950+         sTrackParam = "";
951+         sTrackName = "";
952+       }
953+
954+       QString sId        = QString( "%1/item%2%3")
955                             .arg( sObjectId )
956-                            .arg( sURIParams );
957+                            .arg( sURIParams )
958+                               .arg( sTrackParam );
959 
960-    CDSObject *pItem   = CDSObject::CreateVideoItem( sId,
961-                                                     sName,
962-                                                     sObjectId );
963-    pItem->m_bRestricted  = false;
964-    pItem->m_bSearchable  = true;
965-    pItem->m_sWriteStatus = "WRITABLE";
966+/*    VERBOSE( VB_UPNP, QString( "UpnpCDSTv::AddItem() - sObjectId = %1 sURIParams = %2 sTrackParam = %3" )
967+                                       .arg( sObjectId )
968+                                       .arg( sURIParams )
969+                               .arg(sTrackParam));
970+*/
971+       CDSObject *pItem   = CDSObject::CreateVideoItem( sId, sName+sTrackName, sObjectId);
972+   
973+       pItem->m_bRestricted  = false;
974+       pItem->m_bSearchable  = true;
975+       pItem->m_sWriteStatus = "WRITABLE";
976 
977-    if ( bAddRef )
978-    {
979-        QString sRefId = QString( "%1/0/item%2")
980+       if ( bAddRef )
981+       {
982+               QString sRefId = QString( "%1/0/item%2%3")
983                             .arg( m_sExtensionId )
984-                            .arg( sURIParams     );
985+                            .arg( sURIParams     )
986+                               .arg(sTrackParam);
987 
988-        pItem->SetPropValue( "refID", sRefId );
989-    }
990+               pItem->SetPropValue( "refID", sRefId );
991+       }
992 
993-    pItem->SetPropValue( "genre"          , sCategory    );
994-    pItem->SetPropValue( "longDescription", sDescription );
995-    pItem->SetPropValue( "description"    , sSubtitle    );
996+       pItem->SetPropValue( "genre"          , sCategory    );
997+       pItem->SetPropValue( "longDescription", sDescription );
998+       pItem->SetPropValue( "description"    , sSubtitle    );
999 
1000-    //pItem->SetPropValue( "producer"       , );
1001-    //pItem->SetPropValue( "rating"         , );
1002-    //pItem->SetPropValue( "actor"          , );
1003-    //pItem->SetPropValue( "director"       , );
1004-    //pItem->SetPropValue( "publisher"      , );
1005-    //pItem->SetPropValue( "language"       , );
1006-    //pItem->SetPropValue( "relation"       , );
1007-    //pItem->SetPropValue( "region"         , );
1008 
1009     // ----------------------------------------------------------------------
1010     // Needed for Microsoft Media Player Compatibility
1011     // (Won't display correct Title without them)
1012     // ----------------------------------------------------------------------
1013 
1014-    pItem->SetPropValue( "creator"       , "[Unknown Author]" );
1015-    pItem->SetPropValue( "artist"        , "[Unknown Author]" );
1016-    pItem->SetPropValue( "album"         , "[Unknown Series]" );
1017-    pItem->SetPropValue( "actor"         , "[Unknown Author]" );
1018+       pItem->SetPropValue( "creator"       , "[Unknown Author]" );
1019+       pItem->SetPropValue( "artist"        , "[Unknown Author]" );
1020+       pItem->SetPropValue( "album"         , "[Unknown Series]" );
1021+       pItem->SetPropValue( "actor"         , "[Unknown Author]" );
1022 
1023-    pResults->Add( pItem );
1024+       pResults->Add( pItem );
1025 
1026-    // ----------------------------------------------------------------------
1027-    // Add Video Resource Element based on File extension (HTTP)
1028-    // ----------------------------------------------------------------------
1029+// ----------------------------------------------------------------------
1030+// Add Video Resource Element/Playlist based on File extension (HTTP)
1031+// ----------------------------------------------------------------------
1032     
1033-    QFileInfo fInfo( sBaseName );
1034+       QFileInfo fInfo( sBaseName );
1035+       QString sMimeType = HTTPRequest::GetMimeType( fInfo.extension( FALSE ));
1036+       QString sProtocol = QString( "http-get:*:%1:*" ).arg( sMimeType  );
1037+       QString sURI      = QString( "%1GetRecording%2%3").arg( sURIBase   )
1038+                                                               .arg( sURIParams )
1039+                                                               .arg(sTrackParam);
1040 
1041-    QString sMimeType = HTTPRequest::GetMimeType( fInfo.extension( FALSE ));
1042-    QString sProtocol = QString( "http-get:*:%1:*" ).arg( sMimeType  );
1043-    QString sURI      = QString( "%1GetRecording%2").arg( sURIBase   )
1044-                                                    .arg( sURIParams );
1045+       Resource *pRes = pItem->AddResource( sProtocol, sURI );
1046 
1047-    Resource *pRes = pItem->AddResource( sProtocol, sURI );
1048+       if ( !commflag ) {
1049+         uiStart = dtProgStart.toTime_t();
1050+         uiEnd   = dtProgEnd.toTime_t();
1051+         llSize = nFileSize;
1052+       } else {
1053+         uiStart = uiEnd;
1054+         llStartPos = llEndPos;
1055+         llEndPos = i.data();
1056+         uiEnd = i.key();
1057+         if (trackno == num) {
1058+               uiEnd = dtProgEnd.toTime_t() - dtProgStart.toTime_t();
1059+               llEndPos = nFileSize;
1060+         }
1061+         llSize = llEndPos - llStartPos;
1062+       }
1063 
1064-    uint uiStart = dtProgStart.toTime_t();
1065-    uint uiEnd   = dtProgEnd.toTime_t();
1066-    uint uiDur   = uiEnd - uiStart;
1067+/*      VERBOSE( VB_UPNP, QString( "UpnpCDSTv::AddItem() - trackno = %1 start = %2 end = %3 size = %4" )
1068+                                       .arg( trackno )
1069+                                       .arg( uiStart )
1070+                                       .arg(uiEnd)
1071+                                       .arg(llSize));
1072+*/
1073+       time_t uiDur = uiEnd - uiStart;
1074 
1075-
1076-    QString sDur = QString( "%1:%2:%3" )
1077+       QString sDur = QString( "%1:%2:%3" )
1078                     .arg( (uiDur / 3600) % 24, 2 )
1079                     .arg( (uiDur / 60) % 60  , 2 )
1080                     .arg(  uiDur % 60        , 2 );
1081 
1082-    pRes->AddAttribute( "duration"  , sDur      );
1083-    pRes->AddAttribute( "size"      , longLongToString( nFileSize) );
1084+       pRes->AddAttribute( "duration"  , sDur      );
1085+       pRes->AddAttribute( "size"      , longLongToString( llSize ) );
1086 
1087-/*
1088     // ----------------------------------------------------------------------
1089-    // Add Video Resource Element based on File extension (mythtv)
1090-    // ----------------------------------------------------------------------
1091-
1092-    sProtocol = QString( "myth:*:%1:*"     ).arg( sMimeType  );
1093-    sURI      = QString( "myth://%1/%2" )
1094-                   .arg( m_mapBackendIp  [ sHostName ] )
1095-                   .arg( sBaseName );
1096-
1097-    pRes = pItem->AddResource( sProtocol, sURI );
1098-
1099-    pRes->AddAttribute( "duration"  , sDur      );
1100-    pRes->AddAttribute( "size"      , longLongToString( nFileSize) );
1101-*/
1102-    // ----------------------------------------------------------------------
1103     // Add Thumbnail Resource
1104     // ----------------------------------------------------------------------
1105 
1106-    sURI = QString( "%1GetPreviewImage%2").arg( sURIBase   )
1107+       sURI = QString( "%1GetPreviewImage%2").arg( sURIBase   )
1108                                           .arg( sURIParams );
1109 
1110-    pItem->AddResource( "http-get:*:image/png:*" , sURI );
1111-
1112+       pItem->AddResource( "http-get:*:image/png:*" , sURI );
1113+       ++i;
1114+    } /* for */
1115 }
1116-
1117Index: programs/mythbackend/mythxml.h
1118===================================================================
1119--- programs/mythbackend/mythxml.h      (revision 13462)
1120+++ programs/mythbackend/mythxml.h      (working copy)
1121@@ -4,7 +4,7 @@
1122 // Purpose - Myth XML protocol HttpServerExtension
1123 //                                                                           
1124 // Created By  : David Blain                    Created On : Oct. 24, 2005
1125-// Modified By :                                Modified On:                 
1126+// Modified By : R. Gingher                     Modified On: Jun. 5, 2007                 
1127 //                                                                           
1128 //////////////////////////////////////////////////////////////////////////////
1129 
1130@@ -44,14 +44,15 @@
1131     MXML_GetPreviewImage        =  9,
1132 
1133     MXML_GetRecording           = 10,
1134-    MXML_GetMusic               = 11,
1135+    MXML_GenM3u                 = 11,
1136+    MXML_GetMusic               = 12,
1137 
1138-    MXML_GetExpiring            = 12,
1139-    MXML_GetProgramDetails      = 13,
1140-    MXML_GetVideo               = 14,
1141+    MXML_GetExpiring            = 13,
1142+    MXML_GetProgramDetails      = 14,
1143+    MXML_GetVideo               = 15,
1144 
1145-    MXML_GetConnectionInfo      = 15,
1146-    MXML_GetAlbumArt            = 16
1147+    MXML_GetConnectionInfo      = 16,
1148+    MXML_GetAlbumArt            = 17
1149 
1150 } MythXMLMethod;
1151 
1152@@ -114,14 +115,17 @@
1153         void    GetRecording   ( HttpWorkerThread *pThread,
1154                                  HTTPRequest      *pRequest );
1155 
1156+        void    GenM3u         ( HTTPRequest      *pRequest );
1157+
1158+
1159         void    GetMusic       ( HttpWorkerThread *pThread,
1160                                  HTTPRequest      *pRequest );
1161 
1162         void    GetVideo       ( HttpWorkerThread *pThread,
1163                                  HTTPRequest      *pRequest );
1164 
1165+        void    GetDeviceDesc  ( HTTPRequest *pRequest );
1166 
1167-        void    GetDeviceDesc  ( HTTPRequest *pRequest );
1168         void    GetFile        ( HTTPRequest *pRequest, QString sFileName );
1169 
1170     public:
1171@@ -130,6 +134,8 @@
1172 
1173         bool     ProcessRequest( HttpWorkerThread *pThread, HTTPRequest *pRequest );
1174 
1175+        int       TrackEndMap ( QString sChanId, QDateTime dtStart, frm_pos_map_t &trackMap );
1176+
1177         // Static methods shared with HttpStatus
1178 
1179         static void FillProgramInfo ( QDomDocument *pDoc,
1180Index: programs/mythbackend/upnpcdsmusic.cpp
1181===================================================================
1182--- programs/mythbackend/upnpcdsmusic.cpp       (revision 13462)
1183+++ programs/mythbackend/upnpcdsmusic.cpp       (working copy)
1184@@ -127,7 +127,7 @@
1185 //
1186 /////////////////////////////////////////////////////////////////////////////
1187 
1188-QString UPnpCDSMusic::GetTableName( QString sColumn )
1189+QString UPnpCDSMusic::GetTableName( QString /* sColumn */)
1190 {
1191     return "music_songs song";
1192 }