Forced Subs & Additional Audio/Subtitles Tags support

Discuss issues related to PS3 Media Server development (only for programmers)

Forced Subs & Additional Audio/Subtitles Tags support

Postby ExSport » Sun Sep 12, 2010 2:09 am

Hi all
Who is interested below you can find patch for support of "forced subs" and "additional tags" for audio and subtitle streams(descriptions)
Code: Select all
Index: net/pms/configuration/PmsConfiguration.java
===================================================================
--- net/pms/configuration/PmsConfiguration.java   (revision 410)
+++ net/pms/configuration/PmsConfiguration.java   (working copy)
@@ -123,6 +123,7 @@
    private static final int DEFAULT_SERVER_PORT = 5001;
    private static final int DEFAULT_PROXY_SERVER_PORT = -1;
    private static final String UNLIMITED_BITRATE = "0";
+   private static final String KEY_FORCED_SUBS_TAGS = "forced_subs_tags";
    
    private static final String DEFAULT_AVI_SYNTH_SCRIPT =
         "#AviSynth script is now fully customisable !\n"
@@ -1118,4 +1119,7 @@
    public void setAudioResample(boolean value) {
       configuration.setProperty(KEY_AUDIO_RESAMPLE, value);
    }
+   public String getForcedSubsTags() {
+      return getString(KEY_FORCED_SUBS_TAGS, "forced");
+   }
 }
Index: net/pms/configuration/RendererConfiguration.java
===================================================================
--- net/pms/configuration/RendererConfiguration.java   (revision 410)
+++ net/pms/configuration/RendererConfiguration.java   (working copy)
@@ -303,6 +303,17 @@
       return getBoolean(SHOW_DVD_TITLE_DURATION, false);
    }   
    
+   private static final String SHOW_AUDIO_METADATA="ShowAudioMetadata";
+   private static final String SHOW_SUB_METADATA="ShowSubMetadata";
+   
+   public boolean isShowAudioMetadata() {
+      return getBoolean(SHOW_AUDIO_METADATA, true);
+   }
+   
+   public boolean isShowSubMetadata() {
+      return getBoolean(SHOW_SUB_METADATA, true);
+   }
+   
    private RendererConfiguration() throws ConfigurationException {
       this(null);
    }
Index: net/pms/dlna/DLNAMediaAudio.java
===================================================================
--- net/pms/dlna/DLNAMediaAudio.java   (revision 410)
+++ net/pms/dlna/DLNAMediaAudio.java   (working copy)
@@ -13,6 +13,7 @@
    public int year;
    public int track;
    public int delay;
+   public String flavor;
    
    public DLNAMediaAudio() {
       bitsperSample = 16;
@@ -76,7 +77,7 @@
    }
    
    public String toString() {
-      return "Audio: " + getAudioCodec() + " / lang: " + lang + " / ID: " + id;
+      return "Audio: " + getAudioCodec() + " / lang: " + lang + " / flavor: " + flavor + " / ID: " + id;
    }
 
    @Override
Index: net/pms/dlna/DLNAMediaDatabase.java
===================================================================
--- net/pms/dlna/DLNAMediaDatabase.java   (revision 410)
+++ net/pms/dlna/DLNAMediaDatabase.java   (working copy)
@@ -174,6 +174,7 @@
             sb.append("  FILEID            INT              NOT NULL"); //$NON-NLS-1$
             sb.append(", ID                INT              NOT NULL"); //$NON-NLS-1$
             sb.append(", LANG              VARCHAR2(3)"); //$NON-NLS-1$
+            sb.append(", FLAVOR            VARCHAR2(64)"); //$NON-NLS-1$
             sb.append(", NRAUDIOCHANNELS   NUMERIC"); //$NON-NLS-1$
             sb.append(", SAMPLEFREQ        VARCHAR2(16)"); //$NON-NLS-1$
             sb.append(", CODECA            VARCHAR2(32)"); //$NON-NLS-1$
@@ -192,6 +193,7 @@
             sb.append("  FILEID            INT              NOT NULL"); //$NON-NLS-1$
             sb.append(", ID                INT              NOT NULL"); //$NON-NLS-1$
             sb.append(", LANG              VARCHAR2(3)"); //$NON-NLS-1$
+            sb.append(", FLAVOR            VARCHAR2(64)"); //$NON-NLS-1$
             sb.append(", TYPE              INT"); //$NON-NLS-1$
             sb.append(", constraint PKSUB primary key (FILEID, ID))"); //$NON-NLS-1$
             
@@ -302,6 +304,7 @@
                DLNAMediaAudio audio = new DLNAMediaAudio();
                audio.id = subrs.getInt("ID"); //$NON-NLS-1$
                audio.lang = subrs.getString("LANG"); //$NON-NLS-1$
+               audio.flavor = subrs.getString("FLAVOR"); //$NON-NLS-1$
                audio.nrAudioChannels = subrs.getInt("NRAUDIOCHANNELS"); //$NON-NLS-1$
                audio.sampleFrequency = subrs.getString("SAMPLEFREQ"); //$NON-NLS-1$
                audio.codecA = subrs.getString("CODECA"); //$NON-NLS-1$
@@ -325,6 +328,7 @@
                DLNAMediaSubtitle sub = new DLNAMediaSubtitle();
                sub.id = subrs.getInt("ID"); //$NON-NLS-1$
                sub.lang = subrs.getString("LANG"); //$NON-NLS-1$
+               sub.flavor = subrs.getString("FLAVOR"); //$NON-NLS-1$
                sub.type = subrs.getInt("TYPE"); //$NON-NLS-1$
                media.subtitlesCodes.add(sub);
             }
@@ -405,35 +409,37 @@
          if (media != null && id > -1) {
             PreparedStatement insert = null;
             if (media.audioCodes.size() > 0)
-               insert = conn.prepareStatement("INSERT INTO AUDIOTRACKS VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); //$NON-NLS-1$
+               insert = conn.prepareStatement("INSERT INTO AUDIOTRACKS VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); //$NON-NLS-1$
             for(DLNAMediaAudio audio:media.audioCodes) {
                insert.clearParameters();
                insert.setInt(1, id);
                insert.setInt(2, audio.id);
                insert.setString(3, audio.lang);
-               insert.setInt(4, audio.nrAudioChannels);
-               insert.setString(5, audio.sampleFrequency);
-               insert.setString(6, audio.codecA);
-               insert.setInt(7, audio.bitsperSample);
-               insert.setString(8, StringUtils.trimToEmpty(audio.album));
-               insert.setString(9, StringUtils.trimToEmpty(audio.artist));
-               insert.setString(10, StringUtils.trimToEmpty(audio.songname));
-               insert.setString(11, StringUtils.trimToEmpty(audio.genre));
-               insert.setInt(12, audio.year);
-               insert.setInt(13, audio.track);
-               insert.setInt(14, audio.delay);
+               insert.setString(4, audio.flavor);
+               insert.setInt(5, audio.nrAudioChannels);
+               insert.setString(6, audio.sampleFrequency);
+               insert.setString(7, audio.codecA);
+               insert.setInt(8, audio.bitsperSample);
+               insert.setString(9, StringUtils.trimToEmpty(audio.album));
+               insert.setString(10, StringUtils.trimToEmpty(audio.artist));
+               insert.setString(11, StringUtils.trimToEmpty(audio.songname));
+               insert.setString(12, StringUtils.trimToEmpty(audio.genre));
+               insert.setInt(13, audio.year);
+               insert.setInt(14, audio.track);
+               insert.setInt(15, audio.delay);
                insert.executeUpdate();
             }
             
             if (media.subtitlesCodes.size() > 0)
-               insert = conn.prepareStatement("INSERT INTO SUBTRACKS VALUES (?, ?, ?, ?)"); //$NON-NLS-1$
+               insert = conn.prepareStatement("INSERT INTO SUBTRACKS VALUES (?, ?, ?, ?, ?)"); //$NON-NLS-1$
             for(DLNAMediaSubtitle sub:media.subtitlesCodes) {
                if (sub.file == null) { // no save of external subtitles
                   insert.clearParameters();
                   insert.setInt(1, id);
                   insert.setInt(2, sub.id);
                   insert.setString(3, sub.lang);
-                  insert.setInt(4, sub.type);
+                  insert.setString(4, sub.flavor);
+                  insert.setInt(5, sub.type);
                   insert.executeUpdate();
                }
             }
Index: net/pms/dlna/DLNAMediaInfo.java
===================================================================
--- net/pms/dlna/DLNAMediaInfo.java   (revision 410)
+++ net/pms/dlna/DLNAMediaInfo.java   (working copy)
@@ -29,6 +29,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.ListIterator;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -415,7 +416,9 @@
                ArrayList<String> lines = (ArrayList<String>) pw.getResults();
                int langId = 0;
                int subId = 0;
+               ListIterator<String> FFmpegMetaData = lines.listIterator();
                for(String line:lines) {
+                  FFmpegMetaData.next();
                   line = line.trim();
                   if (line.startsWith("Output"))
                      matchs = false;
@@ -478,7 +481,7 @@
                            }
                         }
                         //
-                        audioCodes.add(audio);
+//                        audioCodes.add(audio);
                         while (st.hasMoreTokens()) {
                            String token = st.nextToken().trim();
                            if (token.startsWith("Stream")) {
@@ -502,8 +505,21 @@
                               audio.bitsperSample = 32;
                            } else if (token.equals("s24")) {
                               audio.bitsperSample = 24;
+                           } else if (token.equals("s16")) {
+                              audio.bitsperSample = 16;
                            }
                         }
+                        int FFmpegMetaDataNr = FFmpegMetaData.nextIndex();
+                        if (FFmpegMetaDataNr > -1) line = lines.get(FFmpegMetaDataNr);
+                        if (line.indexOf("Metadata:") > -1) {
+                           line = lines.get(FFmpegMetaData.nextIndex()+1);
+                           int aa = line.indexOf(": ");
+                           int bb = line.length();
+                           if (aa > -1 && bb > aa) {
+                              audio.flavor = line.substring(aa+2, bb);
+                           }   
+                        }
+                        audioCodes.add(audio);
                      } else if (line.indexOf("Video:") > -1) {
                         StringTokenizer st = new StringTokenizer(line, ",");
                         while (st.hasMoreTokens()) {
@@ -547,7 +563,18 @@
                         } else
                            lang.lang = DLNAMediaLang.UND;
                         lang.id = subId++;
-                        subtitlesCodes.add(lang);
+//                        subtitlesCodes.add(lang);
+                        int FFmpegMetaDataNr = FFmpegMetaData.nextIndex();
+                        if (FFmpegMetaDataNr > -1) line = lines.get(FFmpegMetaDataNr);
+                        if (line.indexOf("Metadata:") > -1) {
+                           line = lines.get(FFmpegMetaData.nextIndex()+1);
+                           int aa = line.indexOf(": ");
+                           int bb = line.length();
+                           if (aa > -1 && bb > aa) {
+                              lang.flavor = line.substring(aa+2, bb);
+                           }   
+                        }
+                         subtitlesCodes.add(lang);
                      }
                   }
                }
@@ -794,12 +821,12 @@
    public String toString() {
       String s = "container: " + container + " / bitrate: " + bitrate + " / size: " + size + " / codecV: " + codecV + " / duration: " + duration + " / width: " + width + " / height: " + height + " / frameRate: " + frameRate + " / thumb size : " + (thumb!=null?thumb.length:0);
       for(DLNAMediaAudio audio:audioCodes) {
-         s += "\n\taudio: id=" + audio.id + " / lang: " + audio.lang + " / codec: " + audio.codecA + " / sf:" + audio.sampleFrequency + " / na: " + audio.nrAudioChannels + " / bs: " + audio.bitsperSample;
+         s += "\n\taudio: id=" + audio.id + " / lang: " + audio.lang + " / flavor: " + audio.flavor + " / codec: " + audio.codecA + " / sf:" + audio.sampleFrequency + " / na: " + audio.nrAudioChannels + " / bs: " + audio.bitsperSample;
          if (audio.artist != null)
             s += " / " + audio.artist + "|" + audio.album + "|" + audio.songname + "|" + audio.year + "|" + audio.track;
       }
       for(DLNAMediaSubtitle sub:subtitlesCodes) {
-         s += "\n\tsub: id=" + sub.id + " / lang: " + sub.lang + " / type: " + sub.type;
+         s += "\n\tsub: id=" + sub.id + " / lang: " + sub.lang + " / flavor: " + sub.flavor + " / type: " + sub.type;
       }
       return s;
    }
Index: net/pms/dlna/DLNAMediaSubtitle.java
===================================================================
--- net/pms/dlna/DLNAMediaSubtitle.java   (revision 410)
+++ net/pms/dlna/DLNAMediaSubtitle.java   (working copy)
@@ -49,7 +49,7 @@
    }
 
    public String toString() {
-      return "Sub: " + getSubType() + " / lang: " + lang + " / ID: " + id + " / FILE: " + (file!=null?file.getAbsolutePath():"-");
+      return "Sub: " + getSubType() + " / lang: " + lang + " / flavor: " + flavor + " / ID: " + id + " / FILE: " + (file!=null?file.getAbsolutePath():"-");
    }
    
    public void checkUnicode() {
Index: net/pms/dlna/DLNAResource.java
===================================================================
--- net/pms/dlna/DLNAResource.java   (revision 410)
+++ net/pms/dlna/DLNAResource.java   (working copy)
@@ -528,10 +528,10 @@
             name += " {External Subtitles}";
       
       if (media_audio != null)
-         name = (player!=null?("[" + player.name() + "]"):"") + " {Audio: " + media_audio.getAudioCodec() + "/" + media_audio.getLang() + "}";
+         name = (player!=null?("[" + player.name() + "]"):"") + " {Audio: " + media_audio.getAudioCodec() + "/" + media_audio.getLang() + ((media_audio.flavor!=null&&mediaRenderer!=null&&mediaRenderer.isShowAudioMetadata())?(" ("+media_audio.flavor+")"):"") + "}";
       
       if (media_subtitle != null && media_subtitle.id != -1)
-         name += " {Sub: " + media_subtitle.getSubType() + "/" + media_subtitle.getLang() + (media_subtitle.flavor!=null?("/"+media_subtitle.flavor):"") +  "}";
+         name += " {Sub: " + media_subtitle.getSubType() + "/" + media_subtitle.getLang() + ((media_subtitle.flavor!=null&&mediaRenderer!=null&&mediaRenderer.isShowSubMetadata())?(" ("+media_subtitle.flavor+")"):"") + "}";
       
       if (avisynth)
          name = (player!=null?("[" + player.name()):"") + " + AviSynth]";
Index: net/pms/dlna/MediaInfoParser.java
===================================================================
--- net/pms/dlna/MediaInfoParser.java   (revision 410)
+++ net/pms/dlna/MediaInfoParser.java   (working copy)
@@ -95,6 +95,11 @@
                            currentAudioTrack.lang = getLang(value);
                         else if (step == MediaInfo.StreamKind.Text)
                            currentSubTrack.lang = getLang(value);
+                     } else if (key.equals("Title")) {
+                        if (step == MediaInfo.StreamKind.Audio)
+                           currentAudioTrack.flavor = getFlavor(value);
+                        else if (step == MediaInfo.StreamKind.Text)
+                           currentSubTrack.flavor = getFlavor(value);
                      } else if (key.equals("Width")) {
                         media.width = getPixelValue(value);
                      } else if (key.equals("Encryption") && !media.encrypted) {
@@ -376,6 +381,11 @@
       return value;
    }
    
+   public static String getFlavor(String value) {
+      value = value.trim();
+      return value;
+   }
+      
    public static String getDuration(String value) {
       int h = 0, m = 0, s = 0;
       StringTokenizer st = new StringTokenizer(value, " ");
Index: net/pms/encoders/Player.java
===================================================================
--- net/pms/encoders/Player.java   (revision 410)
+++ net/pms/encoders/Player.java   (working copy)
@@ -155,7 +155,7 @@
       if (matchedSub != null && params.sid == null) {
          if (matchedSub.lang != null && matchedSub.lang.equals("off")) {
             PMS.debug(" Disabled the subtitles: " + matchedSub);
-            return;
+            //return;
          } else
             params.sid = matchedSub;
       }
@@ -168,16 +168,39 @@
          FileUtil.doesSubtitlesExists(video, media, false);
          
          if (configuration.getUseSubtitles()) {
+            boolean forcedSubsFound = false;
             // priority to external subtitles
             for(DLNAMediaSubtitle sub:media.subtitlesCodes) {
-               PMS.debug("Found subtitles track : " + sub);
-               if (sub.file != null) {
-                  PMS.debug("Found external file : " + sub.file.getAbsolutePath());
-                  params.sid = sub;
-                  break;
+               if (matchedSub !=null && matchedSub.lang !=null && matchedSub.lang.equals("off")) {
+                  StringTokenizer st = new StringTokenizer(configuration.getForcedSubsTags(), ","); //$NON-NLS-1$
+                  while (st != null && sub.flavor != null && st.hasMoreTokens()) {
+                     String forcedTags = st.nextToken();
+                     forcedTags = forcedTags.trim();
+                     if (sub.flavor.toLowerCase().indexOf(forcedTags) > -1) {
+                        if (Iso639.isCodesMatching(sub.lang,configuration.getMencoderSubLanguages())) {
+                           PMS.debug("Forcing prefered subtitles : " + sub.getLang() + "/" + sub.flavor);
+                           PMS.debug("Forced subtitles track : " + sub);
+                           if (sub.file != null) {
+                              PMS.debug("Found external forced file : " + sub.file.getAbsolutePath());
+                           }
+                           params.sid = sub;
+                           forcedSubsFound = true;
+                           break;
+                        }
+                     }
+                  }
+                  if (forcedSubsFound == true) break;
+               } else {
+                  PMS.debug("Found subtitles track : " + sub);
+                  if (sub.file != null) {
+                     PMS.debug("Found external file : " + sub.file.getAbsolutePath());
+                     params.sid = sub;
+                     break;
+                  }
                }
             }
          }
+         if (matchedSub !=null && matchedSub.lang !=null && matchedSub.lang.equals("off")) return;
          
          //
          if (params.sid == null) {

What does it mean and how to use it?
It shows descriptions for AUDIO/SUBTITLE tracks if defined so you can imagine if audio is documentary or main stream, in case of subtitles if it is full, forced or documentary subtitles etc.
In case of subtitles we can use this tag for loading FORCED subtitles automatically (if present).
I added 3 variables for better handling with this addon:
forced_subs_tags can be added to pms.conf file. If not, "forced" string is used as default
example: forced_subs_tags=forced,singing,burned
Here you can define tags which should be considered as forced subtitles.
ShowAudioMetadata and ShowSubMetadata can be added to renderer conf file. If not, TRUE is used as default = description visible if exists
example: ShowAudioMetadata=false
ShowSubMetadata=true
It is configurable on renderer level because some renderers can't scroll long lines so you can disable it on problematic renderers where you loose language part due to very long line.

How to use it:
1. Install latest r410 or newer
2. put update.jar next to pms.exe/pms.sh and pms.jar file(in installed directory) or compile whole build yourself
3. restart PMS and reset Media Library if used (old database doesn't include new additional info about audio/subtitles tags)

Prerequisites for "forced" subs support & displaying additional audio/subtitles info:
You need:
- latest FFmpeg version which supports Metadata (already included from Windows r381 builds and newer, don't know about other OS)
- or enable MediaInfo engine in renderer config (recent build with MediaInfo.dll needed)

How to configure forced subtitles:
To define tag for external subtitle file use this example: moviename.cs-full.srt or moviename.cs-forced.srt etc. Delimiter for tag is "-"
For tagging subtitles in MKV container(if tag is missing), you can use e.g. MKVTOOLNIX(mkvmerge GUI) and add this info as "Track name".
Image

When configured, how to load it automatically?
- you need to enable "Autoload *.srt/*.sub subtitles with the same name" in PMS
- and choose [MENCODER] or [MENCODER]{External Subtitles} inside #--TRANSCODE--# folder
- or choose Movie_Name.avi[MENCODER]{External Subtitles} outside #--TRANSCODE--# folder

That's all 8-)
Last edited by ExSport on Sun Jun 05, 2011 2:09 pm, edited 1 time in total.
ExSport
 
Posts: 2168
Joined: Mon Jan 19, 2009 5:40 pm

Re: Forced Subs & Additional Audio/Subtitles Tags support

Postby ExSport » Fri Oct 01, 2010 2:35 pm

Hi all
It seems I will need help with coding so who is interrested?
In code above I fixed bug which made an exception when chapters were enabled.
But one problem or little feature is not possibe to be done with my poor Java knowledge.
When chapters are enabled, renderer is set to null. This will call first method where mediaRenderer is set to null so it will skip populate RendererConfiguration class ==> isShowAudioMedadata() and isShowSubMetadata() methods are unknown.
Code: Select all
   public String getDisplayName() {
      return getDisplayName(null);
   }
   
   public String getDisplayName(RendererConfiguration mediaRenderer) {
      code code code code code
   }

For now I added "mediaRenderer!=null" so exceptions will not be made but it will not add this audio/sub metadata tag to [Chapters] lines.
Code: Select all
      if (media_audio != null)
         name = (player!=null?("[" + player.name() + "]"):"") + " {Audio: " + media_audio.getAudioCodec() + "/" + media_audio.getLang() + ((media_audio.flavor!=null&&mediaRenderer!=null&&mediaRenderer.isShowAudioMetadata())?(" ("+media_audio.flavor+")"):"") + "}";
      
      if (media_subtitle != null && media_subtitle.id != -1)
         name += " {Sub: " + media_subtitle.getSubType() + "/" + media_subtitle.getLang() + ((media_subtitle.flavor!=null&&mediaRenderer!=null&&mediaRenderer.isShowSubMetadata())?(" ("+media_subtitle.flavor+")"):"") + "}";

QUESTION:
Is it possible to call these methods also for [Chapters]???????
Many thanks
P.S.
Code in first post fixed but it will take me more time to build update.jar again so will be added later(maybe this night).
ExSport
 
Posts: 2168
Joined: Mon Jan 19, 2009 5:40 pm

Re: Forced Subs & Additional Audio/Subtitles Tags support

Postby ExSport » Sun Apr 03, 2011 10:19 am

I made custom build with some addons so who wants test it, it is here: http://code.google.com/p/pms-exsport/
For now only Windows build is compiled, not linux one.
ExSport
 
Posts: 2168
Joined: Mon Jan 19, 2009 5:40 pm


Return to Developers

Who is online

Users browsing this forum: Yahoo [Bot] and 3 guests