[banshee] Support embedded and external subtitles (bgo#534581)



commit 9e86951d4234d3abdd25d301837f86475795687e
Author: Olivier Dufour <olivier duff gmail com>
Date:   Wed Nov 10 23:23:48 2010 +0100

    Support embedded and external subtitles (bgo#534581)
    
    Signed-off-by: Gabriel Burt <gabriel burt gmail com>

 libbanshee/banshee-player-private.h                |    1 +
 libbanshee/banshee-player-video.c                  |   37 ++++-
 libbanshee/banshee-player.c                        |  114 ++++++++++
 libbanshee/libbanshee.sln                          |   47 +++--
 .../Banshee.GStreamer/PlayerEngine.cs              |   58 +++++-
 .../Banshee.GStreamerSharp/PlayerEngine.cs         |   24 ++
 .../Banshee.MediaEngine/NullPlayerEngine.cs        |   17 ++
 .../Banshee.MediaEngine/PlayerEngine.cs            |   15 ++
 .../Banshee.MediaEngine/PlayerEngineService.cs     |   18 ++
 .../Banshee.Gui.Widgets/MainMenu.cs                |    3 +
 .../Banshee.Gui/PlaybackActions.cs                 |   11 +-
 .../Banshee.Gui/PlaybackSubtitleActions.cs         |  220 ++++++++++++++++++++
 .../Banshee.ThickClient/Banshee.ThickClient.csproj |    1 +
 src/Core/Banshee.ThickClient/Makefile.am           |    1 +
 .../Resources/core-ui-actions-layout.xml           |    1 +
 15 files changed, 545 insertions(+), 23 deletions(-)
---
diff --git a/libbanshee/banshee-player-private.h b/libbanshee/banshee-player-private.h
index f3e28a7..4d9bf44 100644
--- a/libbanshee/banshee-player-private.h
+++ b/libbanshee/banshee-player-private.h
@@ -41,6 +41,7 @@
 #include <gdk/gdk.h>
 #include <gst/fft/gstfftf32.h>
 #include <gst/pbutils/pbutils.h>
+#include <gst/tag/tag.h>
 
 #if defined(GDK_WINDOWING_X11)
 #  include <gdk/gdkx.h>
diff --git a/libbanshee/banshee-player-video.c b/libbanshee/banshee-player-video.c
index 8319403..b901354 100644
--- a/libbanshee/banshee-player-video.c
+++ b/libbanshee/banshee-player-video.c
@@ -57,7 +57,7 @@ bp_video_find_xoverlay (BansheePlayer *player)
         g_mutex_unlock (player->video_mutex);
         return FALSE;
     }
-    
+   
     xoverlay = GST_IS_BIN (video_sink)
         ? gst_bin_get_by_interface (GST_BIN (video_sink), GST_TYPE_X_OVERLAY)
         : video_sink;
@@ -89,6 +89,41 @@ bp_video_find_xoverlay (BansheePlayer *player)
 
 #endif /* GDK_WINDOWING_X11 || GDK_WINDOWING_WIN32 */
 
+P_INVOKE int
+bp_get_subtitle_count (BansheePlayer *player)
+{
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), 0);
+
+    int n_text;
+    g_object_get (G_OBJECT (player->playbin), "n-text", &n_text, NULL);
+    return n_text;
+}
+
+P_INVOKE void
+bp_set_subtitle (BansheePlayer *player, int index)
+{
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+
+    int n_text = bp_get_subtitle_count (player);
+
+    if (n_text == 0 || index < -1 || index >= n_text)
+        return;
+
+    bp_debug ("[subtitle]: set subtitle to %d.", index);
+
+    gint flags;
+    g_object_get (G_OBJECT (player->playbin), "flags", &flags, NULL);
+
+    if (index == -1) {
+        flags &= ~(1 << 2);//GST_PLAY_FLAG_TEXT
+        g_object_set (G_OBJECT (player->playbin), "flags", flags, NULL);
+    } else {
+        flags |= (1 << 2);//GST_PLAY_FLAG_TEXT
+        g_object_set (G_OBJECT (player->playbin), "flags", flags, NULL);
+        g_object_set (G_OBJECT (player->playbin), "current-text", index, NULL);
+    }
+}
+
 static void
 bp_video_sink_element_added (GstBin *videosink, GstElement *element, BansheePlayer *player)
 {
diff --git a/libbanshee/banshee-player.c b/libbanshee/banshee-player.c
index ef15efd..03a9d2d 100644
--- a/libbanshee/banshee-player.c
+++ b/libbanshee/banshee-player.c
@@ -49,6 +49,47 @@ bp_pipeline_set_state (BansheePlayer *player, GstState state)
     }
 }
 
+static void
+bp_lookup_for_subtitle (BansheePlayer *player, const gchar *uri)
+{
+    gchar *scheme, *filename, *subfile, *dot, *suburi;
+    int j;
+    // Always enable rendering of subtitles
+    gint flags;
+    g_object_get (G_OBJECT (player->playbin), "flags", &flags, NULL);
+    flags |= (1 << 2);//GST_PLAY_FLAG_TEXT
+    g_object_set (G_OBJECT (player->playbin), "flags", flags, NULL);
+
+    bp_debug ("[subtitle]: lookup for subtitle for video file.");
+    scheme = g_uri_parse_scheme (uri);
+    static gchar *subtitle_extensions[] = { ".srt", ".sub", ".smi", ".txt", ".mpl", ".dks", ".qtx" };
+    if (scheme == NULL || strcmp (scheme, "file") != 0) {
+        g_free (scheme);
+        return;
+    }
+    g_free (scheme);
+
+    dot = g_strrstr (uri, ".");
+    if (dot == NULL)
+        return;
+    filename = g_filename_from_uri (g_strndup (uri, dot - uri), NULL, NULL);
+
+    for (j = 0; j < G_N_ELEMENTS (subtitle_extensions); j++) {
+        subfile = g_strconcat (filename, subtitle_extensions[j], NULL);
+        if (g_file_test (subfile, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
+            bp_debug ("[subtitle]: Found srt file: %s", subfile);
+            suburi = g_filename_to_uri (subfile, NULL, NULL);
+            g_object_set (G_OBJECT (player->playbin), "suburi", suburi, NULL);
+            g_free (suburi);
+            g_free (subfile);
+            g_free (filename);
+            return;
+        }
+        g_free (subfile);
+    }
+    g_free (filename);
+}
+
 // ---------------------------------------------------------------------------
 // Public Functions
 // ---------------------------------------------------------------------------
@@ -128,6 +169,9 @@ bp_open (BansheePlayer *player, const gchar *uri)
     // Pass the request off to playbin
     g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
     
+    // Lookup for subtitle files with same name/folder
+    bp_lookup_for_subtitle (player, uri);
+
     player->in_gapless_transition = FALSE;
     
     return TRUE;
@@ -172,6 +216,7 @@ bp_set_next_track (BansheePlayer *player, const gchar *uri)
     g_return_val_if_fail (IS_BANSHEE_PLAYER (player), FALSE);
     g_return_val_if_fail (player->playbin != NULL, FALSE);
     g_object_set (G_OBJECT (player->playbin), "uri", uri, NULL);
+    bp_lookup_for_subtitle (player, uri);
     return TRUE;
 }
 
@@ -373,3 +418,72 @@ bp_set_about_to_finish_callback (BansheePlayer *player, BansheePlayerAboutToFini
 {
     SET_CALLBACK (about_to_finish_cb);
 }
+
+P_INVOKE void
+bp_set_subtitle_uri (BansheePlayer *player, const gchar *uri)
+{
+    g_return_if_fail (IS_BANSHEE_PLAYER (player));
+    gint64 pos = -1;
+    GstState state;
+    GstFormat format = GST_FORMAT_BYTES;
+    gboolean paused = FALSE;
+
+    // Gstreamer playbin do not support to set suburi during playback
+    // so have to stop/play and seek
+    gst_element_get_state (player->playbin, &state, NULL, 0);
+    paused = (state == GST_STATE_PAUSED);
+    if (state >= GST_STATE_PAUSED) {
+        gst_element_query_position (player->playbin, &format, &pos);
+        gst_element_set_state (player->playbin, GST_STATE_READY);
+        // Force to wait asynch operation
+        gst_element_get_state (player->playbin, &state, NULL, -1);
+    }
+
+    g_object_set (G_OBJECT (player->playbin), "suburi", uri, NULL);
+    gst_element_set_state (player->playbin, paused ? GST_STATE_PAUSED : GST_STATE_PLAYING);
+
+    // Force to wait asynch operation
+    gst_element_get_state (player->playbin, &state, NULL, -1);
+
+    if (pos != -1) {
+        gst_element_seek_simple (player->playbin, format, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, pos);
+    }
+}
+
+P_INVOKE gchar *
+bp_get_subtitle_uri (BansheePlayer *player)
+{
+    gchar *uri;
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), "");
+    g_object_get (G_OBJECT (player->playbin), "suburi", &uri, NULL);
+    return uri;
+}
+
+P_INVOKE gchar *
+bp_get_subtitle_description (BansheePlayer *player, int i)
+{
+    gchar *code;
+    gchar *desc = NULL;
+    GstTagList *tags = NULL;
+
+    g_return_val_if_fail (IS_BANSHEE_PLAYER (player), NULL);
+
+    g_signal_emit_by_name (G_OBJECT (player->playbin), "get-text-tags", i, &tags);
+    if (G_LIKELY(tags)) {
+        gst_tag_list_get_string (tags, GST_TAG_LANGUAGE_CODE, &code);
+        gst_tag_list_free (tags);
+
+        g_return_val_if_fail (code != NULL, NULL);
+
+        // ISO 639-2 undetermined language
+        if (strcmp ((const gchar *)code, "und") == 0) {
+            return NULL;
+        }
+        bp_debug ("[subtitle]: iso 639-2 subtitle code %s", code);
+        desc = (gchar *) gst_tag_get_language_name ((const gchar *)&code);
+        bp_debug ("[subtitle]: subtitle language: %s", desc);
+
+        g_free (code);
+    }
+    return desc;
+}
diff --git a/libbanshee/libbanshee.sln b/libbanshee/libbanshee.sln
index 4b058d4..3a03817 100644
--- a/libbanshee/libbanshee.sln
+++ b/libbanshee/libbanshee.sln
@@ -1,20 +1,27 @@
-
-Microsoft Visual Studio Solution File, Format Version 10.00
-# Visual C++ Express 2008
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbanshee", "libbanshee.vcproj", "{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Win32 = Debug|Win32
-		Release|Win32 = Release|Win32
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Debug|Win32.ActiveCfg = Debug|Win32
-		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Debug|Win32.Build.0 = Debug|Win32
-		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Release|Win32.ActiveCfg = Release|Win32
-		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Release|Win32.Build.0 = Release|Win32
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 10.00
+# Visual Studio 2008
+Project("{2857B73E-F847-4B02-9238-064979017E93}") = "libbanshee", "libbanshee.cproj", "{6B781836-AB65-49EF-BECD-CCC193C5D589}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Win32 = Debug|Win32
+		Release|Win32 = Release|Win32
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{6B781836-AB65-49EF-BECD-CCC193C5D589}.Debug|Win32.ActiveCfg = Debug|Any CPU
+		{6B781836-AB65-49EF-BECD-CCC193C5D589}.Debug|Win32.Build.0 = Debug|Any CPU
+		{6B781836-AB65-49EF-BECD-CCC193C5D589}.Release|Win32.ActiveCfg = Debug|Any CPU
+		{6B781836-AB65-49EF-BECD-CCC193C5D589}.Release|Win32.Build.0 = Debug|Any CPU
+		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Debug|Win32.ActiveCfg = Debug|Win32
+		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Debug|Win32.Build.0 = Debug|Win32
+		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Release|Win32.ActiveCfg = Release|Win32
+		{8045CB14-6CFB-4CBE-9A09-77FAD23B8F83}.Release|Win32.Build.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(MonoDevelopProperties) = preSolution
+		StartupItem = libbanshee.cproj
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
index 1d4cf75..9319dd1 100644
--- a/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamer/Banshee.GStreamer/PlayerEngine.cs
@@ -691,6 +691,47 @@ namespace Banshee.GStreamer
             }
         }
 
+        public override int SubtitleCount {
+            get { return bp_get_subtitle_count (handle); }
+        }
+
+        public override int SubtitleIndex {
+            set { bp_set_subtitle (handle, value); }
+        }
+
+        public override SafeUri SubtitleUri {
+            set {
+                if (value != null) {
+                    IntPtr uri_ptr = GLib.Marshaller.StringToPtrGStrdup (value.AbsoluteUri);
+                    try {
+                        bp_set_subtitle_uri (handle, uri_ptr);
+                    } finally {
+                        GLib.Marshaller.Free (uri_ptr);
+                    }
+                }
+            }
+            get {
+                IntPtr uri_ptr = IntPtr.Zero;
+                try {
+                    uri_ptr = bp_get_subtitle_uri (handle);
+                    string uri = GLib.Marshaller.Utf8PtrToString (uri_ptr);
+                    return new SafeUri(uri);
+                } finally {
+                    GLib.Marshaller.Free (uri_ptr);
+                }
+            }
+        }
+
+        public override string GetSubtitleDescription (int index)
+        {
+            IntPtr desc_ptr = IntPtr.Zero;
+            try {
+                desc_ptr = bp_get_subtitle_description (handle, index);
+                return GLib.Marshaller.Utf8PtrToString (desc_ptr);
+            } finally {
+                GLib.Marshaller.Free (desc_ptr);
+            }
+        }
 
 #region ISupportClutter
 
@@ -949,5 +990,20 @@ namespace Banshee.GStreamer
 
         [DllImport ("libbanshee.dll")]
         private static extern IntPtr clutter_gst_video_sink_new (IntPtr texture);
-    }
+
+        [DllImport ("libbanshee.dll")]
+        private static extern int bp_get_subtitle_count (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_set_subtitle (HandleRef player, int index);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern void bp_set_subtitle_uri (HandleRef player, IntPtr uri);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern IntPtr bp_get_subtitle_uri (HandleRef player);
+
+        [DllImport ("libbanshee.dll")]
+        private static extern IntPtr bp_get_subtitle_description (HandleRef player, int index);
+   }
 }
diff --git a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
index e05fe74..e60320f 100644
--- a/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
+++ b/src/Backends/Banshee.GStreamerSharp/Banshee.GStreamerSharp/PlayerEngine.cs
@@ -175,6 +175,13 @@ namespace Banshee.GStreamerSharp
             OnStateChanged (PlayerState.Paused);
         }
 
+        public override string GetSubtitleDescription (int index)
+        {
+            return playbin.GetTextTags (index)
+             .GetTag (Gst.Tag.LanguageCode)
+             .FirstOrDefault (t => t != null);
+        }
+
         public override ushort Volume {
             get { return (ushort) Math.Round (playbin.Volume * 100.0); }
             set { playbin.Volume = (value / 100.0); }
@@ -229,5 +236,22 @@ namespace Banshee.GStreamerSharp
         public override VideoDisplayContextType VideoDisplayContextType {
             get { return VideoDisplayContextType.Unsupported; }
         }
+
+        public override int SubtitleCount {
+            get { return playbin.NText; }
+        }
+
+        public override int SubtitleIndex {
+            set {
+                if (value >= 0 && value < SubtitleCount) {
+                    playbin.CurrentText = value;
+                }
+            }
+        }
+
+        public override SafeUri SubtitleUri {
+            set { playbin.Suburi = value.AbsoluteUri; }
+            get { return playbin.Suburi; }
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
index 32a846b..5dd3a6a 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/NullPlayerEngine.cs
@@ -93,5 +93,22 @@ namespace Banshee.MediaEngine
             get { return "Null Player Engine"; }
         }
 
+        public override int SubtitleCount {
+            get { return 0; }
+        }
+
+        public override int SubtitleIndex {
+            set { return; }
+        }
+
+        public override SafeUri SubtitleUri {
+            set { return; }
+            get { return null; }
+        }
+
+        public override string GetSubtitleDescription (int index)
+        {
+            return string.Empty;
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
index f34febc..5365747 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngine.cs
@@ -256,6 +256,8 @@ namespace Banshee.MediaEngine
             OnEventChanged (PlayerEvent.TrackInfoUpdated);
         }
 
+        public abstract string GetSubtitleDescription (int index);
+
         public TrackInfo CurrentTrack {
             get { return current_track; }
         }
@@ -318,5 +320,18 @@ namespace Banshee.MediaEngine
             set { }
             get { return IntPtr.Zero; }
         }
+
+        public abstract int SubtitleCount {
+            get;
+        }
+
+        public abstract int SubtitleIndex {
+            set;
+        }
+
+        public abstract SafeUri SubtitleUri {
+            set;
+            get;
+        }
     }
 }
diff --git a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
index 5d52c56..8d30711 100644
--- a/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
+++ b/src/Core/Banshee.Services/Banshee.MediaEngine/PlayerEngineService.cs
@@ -534,6 +534,11 @@ namespace Banshee.MediaEngine
                 CurrentState == PlayerState.Contacting;
         }
 
+        public string GetSubtitleDescription (int index)
+        {
+            return active_engine.GetSubtitleDescription (index);
+        }
+
         private void CheckPending ()
         {
             if (pending_engine != null && pending_engine != active_engine) {
@@ -626,6 +631,19 @@ namespace Banshee.MediaEngine
             get { return ((active_engine is IEqualizer) && active_engine.SupportsEqualizer); }
         }
 
+        public int SubtitleCount {
+            get { return active_engine.SubtitleCount; }
+        }
+
+        public int SubtitleIndex {
+            set { active_engine.SubtitleIndex = value; }
+        }
+
+        public SafeUri SubtitleUri {
+            set { active_engine.SubtitleUri = value; }
+            get { return active_engine.SubtitleUri; }
+        }
+
         public VideoDisplayContextType VideoDisplayContextType {
             get { return active_engine.VideoDisplayContextType; }
         }
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/MainMenu.cs b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/MainMenu.cs
index 2c71331..2879951 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/MainMenu.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/MainMenu.cs
@@ -48,6 +48,9 @@ namespace Banshee.Gui.Widgets
             ((PlaybackShuffleActions)interface_service.FindActionGroup ("PlaybackShuffle")).AttachSubmenu (
                 "/MainMenu/PlaybackMenu/ShuffleMenu");
 
+            ((PlaybackSubtitleActions)interface_service.FindActionGroup ("PlaybackSubtitle")).AttachSubmenu (
+                "/MainMenu/PlaybackMenu/SubtitleMenu");
+
             menu.Show ();
             PackStart (menu, true, true, 0);
         }
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs b/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs
index 25006c5..02de26d 100644
--- a/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackActions.cs
@@ -49,6 +49,7 @@ namespace Banshee.Gui
         private Gtk.Action play_pause_action;
         private PlaybackRepeatActions repeat_actions;
         private PlaybackShuffleActions shuffle_actions;
+        private PlaybackSubtitleActions subtitle_actions;
 
         public PlaybackRepeatActions RepeatActions {
             get { return repeat_actions; }
@@ -58,6 +59,10 @@ namespace Banshee.Gui
             get { return shuffle_actions; }
         }
 
+        public PlaybackSubtitleActions SubtitleActions {
+            get { return subtitle_actions; }
+        }
+
         public PlaybackActions () : base ("Playback")
         {
             ImportantByDefault = false;
@@ -116,6 +121,7 @@ namespace Banshee.Gui
 
             repeat_actions = new PlaybackRepeatActions (Actions);
             shuffle_actions = new PlaybackShuffleActions (Actions, this);
+            subtitle_actions = new PlaybackSubtitleActions (Actions);
         }
 
         private void OnPlayerEvent (PlayerEventArgs args)
@@ -144,9 +150,12 @@ namespace Banshee.Gui
             }
 
             switch (args.Current) {
+                case PlayerState.Loaded:
+                    ShowStopAction ();
+                    subtitle_actions.ReloadEmbeddedSubtitle ();
+                    break;
                 case PlayerState.Contacting:
                 case PlayerState.Loading:
-                case PlayerState.Loaded:
                 case PlayerState.Playing:
                     ShowStopAction ();
                     break;
diff --git a/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackSubtitleActions.cs b/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackSubtitleActions.cs
new file mode 100644
index 0000000..73a8bc9
--- /dev/null
+++ b/src/Core/Banshee.ThickClient/Banshee.Gui/PlaybackSubtitleActions.cs
@@ -0,0 +1,220 @@
+//
+// PlaybackSubtitleActions.cs
+//
+// Author:
+//   Olivier Dufour <olivier duff gmail com>
+//
+// Copyright 2010 Olivier Dufour
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Collections;
+
+using Gtk;
+
+using Banshee.ServiceStack;
+using Banshee.I18n;
+using Banshee.Collection;
+
+using Hyena;
+
+namespace Banshee.Gui
+{
+    public class PlaybackSubtitleActions : BansheeActionGroup, IEnumerable<RadioAction>
+    {
+        private readonly List<RadioActionEntry> embedded_subtitles_actions = new List<RadioActionEntry> ();
+        public event EventHandler Changed;
+        private Menu mainMenu;
+
+        public new bool Sensitive {
+            get { return base.Sensitive; }
+            set {
+                base.Sensitive = value;
+                OnChanged ();
+            }
+        }
+
+        public PlaybackSubtitleActions (InterfaceActionService actionService)
+            : base (actionService, "PlaybackSubtitle")
+        {
+            Actions.AddActionGroup (this);
+
+            Add (new ActionEntry [] {
+                new ActionEntry ("SubtitleMenuAction", null,
+                    Catalog.GetString ("Subtitle"), null,
+                    Catalog.GetString ("Subtitle"), null),
+                new ActionEntry ("LoadSubtitleAction", null,
+                    Catalog.GetString ("Load subtitle"), null,
+                    Catalog.GetString ("Load subtitle file"), OnLoadSubtitleAction)
+            });
+
+            this["SubtitleMenuAction"].Sensitive = true;
+
+            ServiceManager.PlaybackController.TrackStarted += OnPlaybackTrackStarted;
+
+            //TODO: Set default sub
+        }
+
+        private void OnLoadSubtitleAction (object o, EventArgs args)
+        {
+            var chooser = new Banshee.Gui.Dialogs.FileChooserDialog (
+                Catalog.GetString ("Load Subtitle"),
+                ServiceManager.Get<Banshee.Gui.GtkElementsService> ().PrimaryWindow,
+                FileChooserAction.Open
+            );
+
+            chooser.DefaultResponse = ResponseType.Ok;
+            chooser.SelectMultiple = false;
+
+            chooser.AddButton (Stock.Cancel, ResponseType.Cancel);
+            chooser.AddButton (Catalog.GetString ("L_oad"), ResponseType.Ok);
+
+            Hyena.Gui.GtkUtilities.SetChooserShortcuts (chooser,
+                ServiceManager.SourceManager.VideoLibrary.BaseDirectory
+            );
+
+            var filter = new FileFilter();
+            filter.AddMimeType ("text/x-pango-markup");
+            filter.AddMimeType ("text/plain");
+            filter.Name = Catalog.GetString ("Subtitle files");
+            chooser.AddFilter (filter);
+
+            if (chooser.Run () == (int)ResponseType.Ok) {
+                ServiceManager.PlayerEngine.SubtitleUri = new SafeUri (chooser.Uri);
+            }
+
+            chooser.Destroy ();
+        }
+
+        private void OnPlaybackTrackStarted (object o, EventArgs args)
+        {
+            var current_track = ServiceManager.PlaybackController.CurrentTrack;
+
+            if (current_track != null &&
+                (current_track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0) {
+                //TODO: activate load subtitle file menu else unactivate
+            }
+        }
+
+        private void ClearEmbeddedSubtitles ()
+        {
+            foreach (RadioActionEntry action in embedded_subtitles_actions) {
+                this.Remove (action.name);
+            }
+        }
+
+        private void AddEmbeddedSubtitle (int i)
+        {
+            string desc = ServiceManager.PlayerEngine.GetSubtitleDescription (i);
+            if (String.IsNullOrEmpty (desc)) {
+                desc = String.Format (Catalog.GetString ("Subtitle {0}"), i);
+            }
+            RadioActionEntry new_action = new RadioActionEntry (String.Format ("Subtitle{0}", i), null,
+                                                                desc, null,
+                                                                String.Format (Catalog.GetString ("Activate embedded subtitle {0}"), i), i);
+            embedded_subtitles_actions.Add (new_action);
+
+        }
+
+        public void ReloadEmbeddedSubtitle ()
+        {
+            ClearEmbeddedSubtitles ();
+            int sub_count = ServiceManager.PlayerEngine.SubtitleCount;
+            if (sub_count == 0) {
+                RefreshMenu ();
+                return;
+            }
+            embedded_subtitles_actions.Add (new RadioActionEntry ("None", null,
+                                                                  Catalog.GetString ("None"), null,
+                                                                  Catalog.GetString ("Hide subtitles"), -1));
+            for (int i = 0; i < sub_count; i++) {
+                AddEmbeddedSubtitle (i);
+            }
+            Add (embedded_subtitles_actions.ToArray (), 0, OnActionChanged);
+            RefreshMenu ();
+        }
+
+        private void OnActionChanged (object o, ChangedArgs args)
+        {
+            Log.Debug (string.Format ("[sub] Set sub {0}", args.Current.Value));
+            ServiceManager.PlayerEngine.SubtitleIndex = args.Current.Value;
+        }
+
+        private void OnChanged ()
+        {
+            EventHandler handler = Changed;
+            if (handler != null) {
+                handler (this, EventArgs.Empty);
+            }
+        }
+
+        public IEnumerator<RadioAction> GetEnumerator ()
+        {
+            foreach (RadioActionEntry entry in embedded_subtitles_actions) {
+                yield return (RadioAction)this[entry.name];
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator ()
+        {
+            return GetEnumerator ();
+        }
+
+        public void AttachSubmenu (string menuItemPath)
+        {
+            MenuItem menu = Actions.UIManager.GetWidget (menuItemPath) as MenuItem;
+            menu.Submenu = CreateMenu ();
+        }
+
+        private void RefreshMenu ()
+        {
+            foreach (Widget w in mainMenu.Children) {
+                //RadioMenuItems are embedded subtitle ones
+                if (w is RadioMenuItem) {
+                    mainMenu.Remove (w);
+                }
+            }
+            AddEmbeddedSubtitleMenu ();
+            mainMenu.ShowAll ();
+        }
+
+        public Menu CreateMenu ()
+        {
+            mainMenu = new Gtk.Menu ();
+
+            mainMenu.Append (this["LoadSubtitleAction"].CreateMenuItem ());
+            mainMenu.Append (new SeparatorMenuItem ());
+            AddEmbeddedSubtitleMenu ();
+
+            mainMenu.ShowAll ();
+            return mainMenu;
+        }
+
+        public void AddEmbeddedSubtitleMenu ()
+        {
+            foreach (RadioAction action in this) {
+                mainMenu.Append (action.CreateMenuItem ());
+                Log.Debug (string.Format ("[sub] Add {0}", action.Name));
+            }
+        }
+    }
+}
+
diff --git a/src/Core/Banshee.ThickClient/Banshee.ThickClient.csproj b/src/Core/Banshee.ThickClient/Banshee.ThickClient.csproj
index f9a0442..134527b 100644
--- a/src/Core/Banshee.ThickClient/Banshee.ThickClient.csproj
+++ b/src/Core/Banshee.ThickClient/Banshee.ThickClient.csproj
@@ -316,6 +316,7 @@
     <Compile Include="Banshee.Gui.Widgets\CoverArtDisplay.cs" />
     <Compile Include="Banshee.CairoGlyphs\BansheeLineLogo.cs" />
     <Compile Include="Banshee.Gui\IGlobalUIActions.cs" />
+    <Compile Include="Banshee.Gui\PlaybackSubtitleActions.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>
diff --git a/src/Core/Banshee.ThickClient/Makefile.am b/src/Core/Banshee.ThickClient/Makefile.am
index 62b6fb5..acebf12 100644
--- a/src/Core/Banshee.ThickClient/Makefile.am
+++ b/src/Core/Banshee.ThickClient/Makefile.am
@@ -123,6 +123,7 @@ SOURCES =  \
 	Banshee.Gui/PlaybackActions.cs \
 	Banshee.Gui/PlaybackRepeatActions.cs \
 	Banshee.Gui/PlaybackShuffleActions.cs \
+	Banshee.Gui/PlaybackSubtitleActions.cs \
 	Banshee.Gui/SourceActions.cs \
 	Banshee.Gui/TrackActions.cs \
 	Banshee.Gui/ViewActions.cs \
diff --git a/src/Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml b/src/Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml
index f614071..8c60550 100644
--- a/src/Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml
+++ b/src/Core/Banshee.ThickClient/Resources/core-ui-actions-layout.xml
@@ -108,6 +108,7 @@
       <placeholder name="PlaybackMenuAdditions"/>
       <menuitem name="RepeatMenu" action="RepeatMenuAction"/>
       <menuitem name="ShuffleMenu" action="ShuffleMenuAction"/>
+      <menuitem name="SubtitleMenu" action="SubtitleMenuAction"/>
     </menu>
     
     <menu name="ToolsMenu" action="ToolsMenuAction">



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