[rygel] core: Add basic support for subtitles



commit ec9c6b677889a9cae11c066f260aa56a2086ea3a
Author: Andreas Henriksson <andreas fatal se>
Date:   Thu Apr 8 02:19:47 2010 +0300

    core: Add basic support for subtitles
    
    For each video item, we check if there is a file in the same directory
    with the same name but extension "srt" and if so, we serve it using
    Samsung's custom DIDL-Lite and HTTP extensions.
    
    Co-author & reviewer: Zeeshan Ali (Khattak) <zeeshanak gnome org>

 src/rygel/Makefile.am                      |    2 +
 src/rygel/rygel-http-byte-seek.vala        |    5 ++-
 src/rygel/rygel-http-get-handler.vala      |   14 +++++
 src/rygel/rygel-http-get.vala              |    5 ++
 src/rygel/rygel-http-identity-handler.vala |   14 ++++-
 src/rygel/rygel-http-item-uri.vala         |   10 ++++
 src/rygel/rygel-http-server.vala           |   26 +++++++++-
 src/rygel/rygel-media-item.vala            |   23 ++++++++
 src/rygel/rygel-subtitle-manager.vala      |   76 ++++++++++++++++++++++++++++
 src/rygel/rygel-subtitle.vala              |   54 ++++++++++++++++++++
 src/rygel/rygel-transcode-manager.vala     |    1 +
 src/rygel/rygel-transcoder.vala            |    1 +
 12 files changed, 226 insertions(+), 5 deletions(-)
---
diff --git a/src/rygel/Makefile.am b/src/rygel/Makefile.am
index b093db1..28afb3e 100644
--- a/src/rygel/Makefile.am
+++ b/src/rygel/Makefile.am
@@ -75,6 +75,8 @@ VAPI_SOURCE_FILES = rygel-configuration.vala \
 		    rygel-media-item.vala \
 		    rygel-thumbnail.vala \
 		    rygel-thumbnailer.vala \
+		    rygel-subtitle.vala \
+		    rygel-subtitle-manager.vala \
 		    rygel-browse.vala \
 		    rygel-search.vala \
 			rygel-xbox-hacks.vala \
diff --git a/src/rygel/rygel-http-byte-seek.vala b/src/rygel/rygel-http-byte-seek.vala
index 08257b8..51ea055 100644
--- a/src/rygel/rygel-http-byte-seek.vala
+++ b/src/rygel/rygel-http-byte-seek.vala
@@ -29,6 +29,8 @@ internal class Rygel.HTTPByteSeek : Rygel.HTTPSeek {
 
         if (request.thumbnail != null) {
             total_length = request.thumbnail.size;
+        } else if (request.subtitle != null) {
+            total_length = request.subtitle.size;
         } else {
             total_length = request.item.size;
         }
@@ -80,7 +82,8 @@ internal class Rygel.HTTPByteSeek : Rygel.HTTPSeek {
     public static bool needed (HTTPGet request) {
         return (request.item.size > 0 &&
                 request.handler is HTTPIdentityHandler) ||
-               (request.thumbnail != null && request.thumbnail.size > 0);
+               (request.thumbnail != null && request.thumbnail.size > 0) ||
+               (request.subtitle != null && request.subtitle.size > 0);
     }
 
     public override void add_response_headers () {
diff --git a/src/rygel/rygel-http-get-handler.vala b/src/rygel/rygel-http-get-handler.vala
index fe9c354..4beeea5 100644
--- a/src/rygel/rygel-http-get-handler.vala
+++ b/src/rygel/rygel-http-get-handler.vala
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2008-2010 Nokia Corporation.
+ * Copyright (C) 2010 Andreas Henriksson <andreas fatal se>
  *
  * Author: Zeeshan Ali (Khattak) <zeeshanak gnome org>
  *                               <zeeshan ali nokia com>
@@ -56,6 +57,19 @@ internal abstract class Rygel.HTTPGetHandler: GLib.Object {
             warning ("Received request for 'contentFeatures.dlna.org' but " +
                      "failed to provide the value in response headers");
         }
+
+        // Handle Samsung DLNA TV proprietary subtitle headers
+        if (request.msg.request_headers.get ("getCaptionInfo.sec") != null &&
+            request.item.subtitles.size > 0) {
+                var caption_uri = request.http_server.create_uri_for_item (
+                                        request.item,
+                                        -1,
+                                        0, // FIXME: offer first subtitle only?
+                                        null);
+
+                request.msg.response_headers.append ("CaptionInfo.sec",
+                                                     caption_uri);
+        }
     }
 
     // Create an HTTPResponse object that will render the body.
diff --git a/src/rygel/rygel-http-get.vala b/src/rygel/rygel-http-get.vala
index ecd0a07..e3c5ee8 100644
--- a/src/rygel/rygel-http-get.vala
+++ b/src/rygel/rygel-http-get.vala
@@ -30,9 +30,11 @@ using Gst;
  */
 internal class Rygel.HTTPGet : HTTPRequest {
     public Thumbnail thumbnail;
+    public Subtitle subtitle;
     public HTTPSeek seek;
 
     private int thumbnail_index;
+    private int subtitle_index;
 
     public HTTPGetHandler handler;
 
@@ -42,6 +44,7 @@ internal class Rygel.HTTPGet : HTTPRequest {
         base (http_server, server, msg);
 
         this.thumbnail_index = -1;
+        this.subtitle_index = -1;
     }
 
     protected override async void handle () {
@@ -84,6 +87,8 @@ internal class Rygel.HTTPGet : HTTPRequest {
         if (this.uri.thumbnail_index >= 0) {
             this.thumbnail = this.item.thumbnails.get (
                                         this.uri.thumbnail_index);
+        } else if (this.uri.subtitle_index >= 0) {
+            this.subtitle = this.item.subtitles.get (this.uri.subtitle_index);
         }
     }
 
diff --git a/src/rygel/rygel-http-identity-handler.vala b/src/rygel/rygel-http-identity-handler.vala
index 9db8625..640b1f5 100644
--- a/src/rygel/rygel-http-identity-handler.vala
+++ b/src/rygel/rygel-http-identity-handler.vala
@@ -32,7 +32,10 @@ internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
 
     public override void add_response_headers (HTTPGet request)
                                                throws HTTPRequestError {
-        if (request.thumbnail != null) {
+        if (request.subtitle != null) {
+           request.msg.response_headers.append ("Content-Type",
+                                                request.subtitle.mime_type);
+        } else if (request.thumbnail != null) {
             request.msg.response_headers.append ("Content-Type",
                                                  request.thumbnail.mime_type);
         } else {
@@ -70,7 +73,14 @@ internal class Rygel.HTTPIdentityHandler : Rygel.HTTPGetHandler {
     }
 
     private HTTPResponse render_body_real (HTTPGet request) throws Error {
-        if (request.thumbnail != null) {
+        if (request.subtitle != null) {
+            return new SeekableResponse (request.server,
+                                         request.msg,
+                                         request.subtitle.uri,
+                                         request.seek,
+                                         request.subtitle.size,
+                                         this.cancellable);
+        } else if (request.thumbnail != null) {
             return new SeekableResponse (request.server,
                                          request.msg,
                                          request.thumbnail.uri,
diff --git a/src/rygel/rygel-http-item-uri.vala b/src/rygel/rygel-http-item-uri.vala
index ca92df4..7cf8980 100644
--- a/src/rygel/rygel-http-item-uri.vala
+++ b/src/rygel/rygel-http-item-uri.vala
@@ -26,15 +26,18 @@
 internal class Rygel.HTTPItemURI : Object {
     public string item_id;
     public int thumbnail_index;
+    public int subtitle_index;
     public string? transcode_target;
     public HTTPServer http_server;
 
     public HTTPItemURI (string     item_id,
                         HTTPServer http_server,
                         int        thumbnail_index = -1,
+                        int        subtitle_index = -1,
                         string?    transcode_target = null) {
         this.item_id = item_id;
         this.thumbnail_index = thumbnail_index;
+        this.subtitle_index = subtitle_index;
         this.transcode_target = transcode_target;
         this.http_server = http_server;
     }
@@ -44,6 +47,7 @@ internal class Rygel.HTTPItemURI : Object {
                                     throws HTTPRequestError {
         // do not decode the path here as it may contain encoded slashes
         this.thumbnail_index = -1;
+        this.subtitle_index = -1;
         this.transcode_target = null;
         this.http_server = http_server;
 
@@ -72,6 +76,10 @@ internal class Rygel.HTTPItemURI : Object {
                     this.thumbnail_index = parts[i + 1].to_int ();
 
                     break;
+                case "subtitle":
+                    this.subtitle_index = parts[i + 1].to_int ();
+
+                    break;
                 default:
                     break;
             }
@@ -97,6 +105,8 @@ internal class Rygel.HTTPItemURI : Object {
             path += "/transcoded/" + escaped;
         } else if (this.thumbnail_index >= 0) {
             path += "/thumbnail/" + this.thumbnail_index.to_string ();
+        } else if (this.subtitle_index >= 0) {
+            path += "/subtitle/" + this.subtitle_index.to_string ();
         }
 
         return this.create_uri_for_path (path);
diff --git a/src/rygel/rygel-http-server.vala b/src/rygel/rygel-http-server.vala
index 9b46441..a16d904 100644
--- a/src/rygel/rygel-http-server.vala
+++ b/src/rygel/rygel-http-server.vala
@@ -66,6 +66,23 @@ internal class Rygel.HTTPServer : Rygel.TranscodeManager, Rygel.StateMachine {
     internal override void add_resources (DIDLLiteItem didl_item,
                                           MediaItem    item)
                                           throws Error {
+        // Subtitles first
+        foreach (var subtitle in item.subtitles) {
+            if (!is_http_uri (subtitle.uri)) {
+                var uri = subtitle.uri; // Save the original URI
+                var index = item.subtitles.index_of (subtitle);
+
+                subtitle.uri = this.create_uri_for_item (item,
+                                                         -1,
+                                                         index,
+                                                         null);
+                subtitle.add_didl_node (didl_item);
+
+                // Now restore the original URI
+                subtitle.uri = uri;
+            }
+        }
+
         if (!this.http_uri_present (item)) {
             this.add_proxy_resource (didl_item, item);
         }
@@ -78,7 +95,10 @@ internal class Rygel.HTTPServer : Rygel.TranscodeManager, Rygel.StateMachine {
                 var uri = thumbnail.uri; // Save the original URI
                 var index = item.thumbnails.index_of (thumbnail);
 
-                thumbnail.uri = this.create_uri_for_item (item, index, null);
+                thumbnail.uri = this.create_uri_for_item (item,
+                                                          index,
+                                                          -1,
+                                                          null);
                 thumbnail.add_resource (didl_item,  this.get_protocol ());
 
                 // Now restore the original URI
@@ -90,7 +110,7 @@ internal class Rygel.HTTPServer : Rygel.TranscodeManager, Rygel.StateMachine {
     internal void add_proxy_resource (DIDLLiteItem didl_item,
                                       MediaItem    item)
                                       throws Error {
-        var uri = this.create_uri_for_item (item, -1, null);
+        var uri = this.create_uri_for_item (item, -1, -1, null);
 
         item.add_resource (didl_item,
                            uri.to_string (),
@@ -127,10 +147,12 @@ internal class Rygel.HTTPServer : Rygel.TranscodeManager, Rygel.StateMachine {
 
     internal override string create_uri_for_item (MediaItem item,
                                                   int       thumbnail_index,
+                                                  int       subtitle_index,
                                                   string?   transcode_target) {
         var uri = new HTTPItemURI (item.id,
                                    this,
                                    thumbnail_index,
+                                   subtitle_index,
                                    transcode_target);
 
         return uri.to_string ();
diff --git a/src/rygel/rygel-media-item.vala b/src/rygel/rygel-media-item.vala
index 68b9d70..85e0eac 100644
--- a/src/rygel/rygel-media-item.vala
+++ b/src/rygel/rygel-media-item.vala
@@ -64,6 +64,7 @@ public class Rygel.MediaItem : MediaObject {
     public int color_depth = -1;
 
     public ArrayList<Thumbnail> thumbnails;
+    public ArrayList<Subtitle> subtitles;
 
     internal bool place_holder = false;
 
@@ -77,6 +78,7 @@ public class Rygel.MediaItem : MediaObject {
         this.upnp_class = upnp_class;
 
         this.thumbnails = new ArrayList<Thumbnail> ();
+        this.subtitles = new ArrayList<Subtitle> ();
     }
 
     // Live media items need to provide a nice working implementation of this
@@ -127,6 +129,19 @@ public class Rygel.MediaItem : MediaObject {
                 this.thumbnails.add (thumb);
             } catch (Error err) {}
         }
+
+        if (this.upnp_class.has_prefix (MediaItem.VIDEO_CLASS)) {
+            var subtitle_manager = SubtitleManager.get_default ();
+
+            if (subtitle_manager == null) {
+                return;
+            }
+
+            try {
+                var subtitle = subtitle_manager.get_subtitle (uri);
+                this.subtitles.add (subtitle);
+            } catch (Error err) {}
+        }
     }
 
     internal int compare_transcoders (void *a, void *b) {
@@ -140,6 +155,14 @@ public class Rygel.MediaItem : MediaObject {
     internal void add_resources (DIDLLiteItem didl_item,
                                  bool         allow_internal)
                                  throws Error {
+        foreach (var subtitle in this.subtitles) {
+            var protocol = this.get_protocol_for_uri (subtitle.uri);
+
+            if (allow_internal || protocol != "internal") {
+                subtitle.add_didl_node (didl_item);
+            }
+        }
+
         foreach (var uri in this.uris) {
             var protocol = this.get_protocol_for_uri (uri);
 
diff --git a/src/rygel/rygel-subtitle-manager.vala b/src/rygel/rygel-subtitle-manager.vala
new file mode 100644
index 0000000..7ecf86d
--- /dev/null
+++ b/src/rygel/rygel-subtitle-manager.vala
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Andreas Henriksson <andreas fatal se>.
+ *
+ * Authors: Andreas Henriksson <andreas fatal se>
+ *          Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+internal errordomain SubtitleManagerError {
+    NO_SUBTITLE
+}
+
+/**
+ * Provides subtitles for vidoes.
+ */
+internal class Rygel.SubtitleManager : GLib.Object {
+    private static SubtitleManager manager; // Our singleton object
+
+    public static SubtitleManager? get_default () {
+        if (manager == null) {
+            manager = new SubtitleManager ();
+        }
+
+        return manager;
+    }
+
+    public Subtitle get_subtitle (string uri) throws Error {
+        var video_file = File.new_for_uri (uri);
+
+        var directory = video_file.get_parent ();
+        var filename = video_file.get_basename ();
+        var extension = filename.rchr (-1, '.');
+        if (extension != null) {
+            filename = filename.substring (0,
+                                           filename.length - extension.length);
+        }
+        // FIXME: foreach ".eng.srt", ".ger.srt", ".srt"...
+        // FIXME: case insensitive?
+        filename += ".srt";
+
+        var srt_file = directory.get_child (filename);
+
+        var info = srt_file.query_info (FILE_ATTRIBUTE_ACCESS_CAN_READ + "," +
+                                        FILE_ATTRIBUTE_STANDARD_SIZE,
+                                        FileQueryInfoFlags.NONE,
+                                        null);
+
+        if (!info.get_attribute_boolean (FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
+            throw new SubtitleManagerError.NO_SUBTITLE (
+                                            "No subtitle available");
+        }
+
+        var subtitle = new Subtitle ();
+        subtitle.uri = srt_file.get_uri ();
+        subtitle.size = (long) info.get_attribute_uint64 (
+                                        FILE_ATTRIBUTE_STANDARD_SIZE);
+
+        return subtitle;
+    }
+}
diff --git a/src/rygel/rygel-subtitle.vala b/src/rygel/rygel-subtitle.vala
new file mode 100644
index 0000000..17d531f
--- /dev/null
+++ b/src/rygel/rygel-subtitle.vala
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2008 Zeeshan Ali <zeenix gmail com>.
+ * Copyright (C) 2010 Andreas Henriksson <andreas fatal se>
+ *
+ * Authors: Andreas Henriksson <andreas fatal se>
+ *          Zeeshan Ali (Khattak) <zeeshanak gnome org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using GUPnP;
+
+/**
+ * Represents a subtitle for a video.
+ */
+public class Rygel.Subtitle {
+    public string uri;
+    public string mime_type;
+    public string caption_type;
+
+    public long size = -1;   // Size in bytes
+
+    public Subtitle (string mime_type = "text/plain",
+                     string caption_type = "srt") {
+        this.mime_type = mime_type;
+        this.caption_type = caption_type;
+    }
+
+    internal void add_didl_node (DIDLLiteItem didl_item) {
+        Xml.Node *item_node = didl_item.xml_node;
+        Xml.Node *root_node = item_node->doc->get_root_element ();
+
+        weak Xml.Ns sec_ns = root_node->new_ns ("http://www.sec.co.kr/";, "sec");
+        Xml.Node *sec_node = item_node->new_child (sec_ns,
+                                                   "CaptionInfoEx",
+                                                   this.uri);
+
+        sec_node->new_prop ("sec:type", this.caption_type);
+    }
+}
diff --git a/src/rygel/rygel-transcode-manager.vala b/src/rygel/rygel-transcode-manager.vala
index cedecfb..4fed0fe 100644
--- a/src/rygel/rygel-transcode-manager.vala
+++ b/src/rygel/rygel-transcode-manager.vala
@@ -58,6 +58,7 @@ internal abstract class Rygel.TranscodeManager : GLib.Object {
 
     public abstract string create_uri_for_item (MediaItem  item,
                                                 int        thumbnail_index,
+                                                int        subtitle_index,
                                                 string?    transcode_target);
 
     public virtual void add_resources (DIDLLiteItem didl_item,
diff --git a/src/rygel/rygel-transcoder.vala b/src/rygel/rygel-transcoder.vala
index 16b6ca8..bc3e13a 100644
--- a/src/rygel/rygel-transcoder.vala
+++ b/src/rygel/rygel-transcoder.vala
@@ -67,6 +67,7 @@ internal abstract class Rygel.Transcoder : GLib.Object {
         var protocol = manager.get_protocol ();
         var uri = manager.create_uri_for_item (item,
                                                -1,
+                                               -1,
                                                this.dlna_profile);
         var res = item.add_resource (didl_item, uri, protocol);
         res.size = -1;



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]