Click here to Skip to main content
Click here to Skip to main content

Tagged as

Windows Media Player Native Subtitle Plugin (C++) - Part 01

, 7 Jul 2014
Rate this:
Please Sign up or sign in to vote.
Describes development procedure of a Wndows Media Player Plugin that will allow us to display captions using common subtitle format files such as srt

Table of Contents

Introduction

Here's a screenshot of Windows Media Player with the plugin running:

This article discusses implementation, logic, source code and building procedure of a Subtitle Plug-in for Windows Media Player. If you are interested in understanding the source-code you can go through the code related discussions. Otherwise, to install this plugin you can skip to the section, Installing Plug-in without building from Code. Updated source-code of the project is available at Google Code

The Problem

While most of the players support common formats (srt, sub, ssa/ass) of captions/subtitles, still now Windows Media Player (WMP) does not support them or feature has not been implemented to render captions directory from them. For this reason, we don’t find a way to enable subtitles for common formats in WMP. However, it supports SAMI captions. In this article, we are going develop a plugin to support other types of captions such as subrip (srt).

Let’s briefly talk about solutions that are available.

Background

Popular Solutions using Other Players

One of the most popular alternative is DirectVobSub. Forked from VobSub and previously known as VSFilter it supports 8 popular formats of subtitles/captions. Players such as VLC, Media Player Classic and KMPlayer uses it to render subtitles and uses directshow to display them in Windows.

One interesting player is DivX Player which uses Microsoft Media Foundation to render Videos and Captions and, therefore, they don't use DirectVobSub.

Popular Solutions using WMP Directshow

With updates to WMP it still includes directshow along with Media Foundation. Therefore, it is possible to enable subtitles using directshow filters. Following directshow filter based projects can enable subtitle on WMP:

WMP 'Native' Solution

Recent editions of Windows Media Player uses Microsoft Media Foundation. Media Foundation has some advantages and performance improvements. Using a directshow filter to render captions with Media Foundation hurts that advancement. Performance issue is noticeable sometimes. It is why, our target is to enable captions in WMP for some unsupported subtitle formats without altering media foundation pipeline. Here is a project that does not use direct-show: http://sourceforge.net/projects/wmpsub/files/ and meets mentioned goals. However, this project is not open-source and does not give us interesting development scenario.

Our target is to build a plugin for Windows Media Player that will:

  • Create supported SAMI caption from unsupported format
  • Enable displaying this caption while playing the video
  • Provide settings to configure this plugin

Because of the plugin we'll be able to load captions with Video when opened with Media Player.

Developing plugin for Windows Media Player

Setting up Plugin Wizard with Visual Studio

Windows Media Player SDK on msdn provides documentation on development of Windows Media Player Plug-ins. Therefore, for detailed documentation on the subject please navigate to Windows Media Player Plug-ins on msdn.

First step in developing WMP Plugin is to install plugin wizard. Please follow instructions at Getting Started with the Plug-in Wizard. The webpage suggests that you install "Windows SDK, which includes the Windows Media Player SDK". Regarding this, not all Windows SDK include Windows Media Player SDK. Specifically, Windows SDK 7.0 provides this. I have not been able find one with Windows SDK 8.

According to the documentation page, you have to modify wmpwiz.vsz to fix Wizard Version and absolute path for Wizard location to make the Wizard for your Visual Studio. Wizard version has to be set according to your Visual Studio Version. For example, 11.0 is for Visual Studio 2012 and, 12.0 is for Visual Studio 2013. Absolute path can be set to the location where your wizard setup files exist and can be different based on where have extracted the SDK. If you have installed the SDK, default location works well. Here's a sample wmpwiz.vsz I am using for Visual Studio 2013.

VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.12.0

Param="WIZARD_NAME = Windows Media Player Plug-in Wizard"
Param="ABSOLUTE_PATH = F:\WinSDK 7.0\multimedia\WMP\Wizards\wmpwiz"
Param="FALLBACK_LCID = 1033"

I copied 3 files to "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcprojects". Afterwards, we are able to create a new plugin project following Using the Plug-in Wizard with Visual Studio. Here's a screenshot that displays Windows Media Player Plugin in New Project dialog box.

Choosing Plugin Type

There are 4 types of WMP Plugin:

  • Visualization Plugin
  • UI Plugin
  • DSP Plugin
  • Rendering Plugin (Deprecated)

UI Plugins are several types

  • Display Area Plug-ins (deprecated)
  • Settings Area Plug-ins
  • Metadata Area Plug-ins (deprecated)
  • Separate Window Plug-ins
  • Background Plug-ins

We are going to work on Background Plug-in as our plugin does some operations in the background to set up the caption when a media file is opened. For this reason, when Media Player Plugin Wizard dialog appears we select UI Plugin as displayed on the screen-shot below,

Afterwards, Visual Studio creates required files and templates for the project that are ready to modify.

Code Analysis

Core Logic

Our Plugin application precisely does following things:

  • When a file is opened on Windows Media Player it is an event named wmposMediaOpen. When this event occurs we perform following:
  • Check the extension of opened media file if it can support captions. For example, audio files and some video formats don't support captions.
  • If caption is supported then we check if sami (.smi) caption file already exists. SAMI caption support is built-in. Hence if such a caption for the media file already exists it is automatically loaded by WMP.
  • When such a supported caption file is not found, in this stage, we look for unsupported caption file such as subrip (.srt). We convert such file when we find one. Currently, this application only supports converting caption files with .srt extension. However, it is easy to update this application to support conversion from other formats as well.

Besides that, I have also tried to optimize code and follow standard procedure to ignore any kind of memory leak as I am using C++.

Basic Functions and Use of Code

Following functions implement the operations mentioned in 'core logic' section:

UpdateMediaFilePath - gets the path of opened media file.

FileExtNotSupportedByPlugin - for file extensions not supported this function returns true. Following file extensions are listed not to be supported primarily because they are audio files.

  1. .m4a
  2. .mp3
  3. .wma
  4. .wav
  5. .mp2
  6. .ivf
  7. .mpa
  8. .m3u
  9. .wax
  10. .cda
  11. .mid
  12. .midi
  13. .rmi
  14. .au
  15. .aac

CaptionAlreadyAvailable - this function returns true when SAMI caption file for the media file is found.

EnableCaptionFromSubtitle - finds existing subtitle in unsupported format, converts it and loads it. -

We add our code inside events cpp file (WMPNativeSubtitleevents.cpp)

case wmposMediaOpen:
{
 // Update because file can be reopened
 UpdateMediaFilePath();

 // if file extension has no support for caption
  if (FileExtNotSupportedByPlugin(m_sFilePath))
   break;

 // if (smi or sami exist) do nothing
 if (CaptionAlreadyAvailable())
  break;
 if (EnableCaptionFromSubtitle() == FALSE) {
  // add more cool stuff
  break;
 }
 break;
}

The functions that have been called from this source file are defined in WMPNativeSubtitle.cpp. Let's mention what we add in that source file and header file. Following declarations are added to header file "WMPNativeSubtitle.h" inside declaration of class CWMPNativeSubtitle:

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle
class ATL_NO_VTABLE CWMPNativeSubtitle : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CWMPNativeSubtitle, &CLSID_WMPNativeSubtitle>,
    public IWMPEvents,
    public IWMPPluginUI
{
public:
    CWMPNativeSubtitle();
    ~CWMPNativeSubtitle();

DECLARE_REGISTRY_RESOURCEID(IDR_WMPNativeSubtitle)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CWMPNativeSubtitle)
    COM_INTERFACE_ENTRY(IWMPEvents)
    COM_INTERFACE_ENTRY(IWMPPluginUI)
END_COM_MAP()

  // ... ...
  // Added by Me
private:
 LPTSTR  m_sFilePath;

 BOOL EnableCaptionFromSubtitle();
 BOOL CaptionAlreadyAvailable();
 LPTSTR GetSubtitleFilePath();
 void UpdateMediaFilePath();
};

At the end of same header file we add following declarations:

BOOL FAILMSG(HRESULT hr);
BOOL FileExists(TCHAR * file);
BOOL FileExtNotSupportedByPlugin(LPCTSTR sFile);
BOOL StringEndsWith(LPCTSTR str, LPCTSTR suffix);

We include header file for conversion from srt to SAMI in cpp file (WMPNativeSubtitle.cpp).

#include "SAMIConversion.h"  

Afterwards, we add function definitions into the same cpp file:

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle::UpdateMediaFilePath
// Updates member variable string containing media file path
void CWMPNativeSubtitle::UpdateMediaFilePath() {
 // prepare pointer for update
 if (m_sFilePath)
  delete m_sFilePath;

 // set mediafilename
 BSTR sFileName;
 HRESULT hr = m_spCore->get_URL(&sFileName);
 if (FAILMSG(hr))
  return;
 // 1 for terminating NULL char
 const int sfnSize = SysStringLen(sFileName)+1;
 // will be freed during destruction of object
 m_sFilePath = new TCHAR[sfnSize];
 _tcscpy_s(m_sFilePath, sfnSize, sFileName);
 ::SysFreeString(sFileName);
}

The function allocates necessary space and acquires media file path using get_URL method of COM interface IWMPCore.

BOOL FileExtNotSupportedByPlugin(LPCTSTR sFile) {
 if (StringEndsWith(sFile, TEXT(".m4a")) || StringEndsWith(sFile, TEXT(".mp3")) || StringEndsWith(sFile, TEXT(".wma")) || StringEndsWith(sFile, TEXT(".wav")) || \
  StringEndsWith(sFile, TEXT(".mp2")) || StringEndsWith(sFile, TEXT(".ivf")) || StringEndsWith(sFile, TEXT(".mpa")) || StringEndsWith(sFile, TEXT(".m3u")) || \
  StringEndsWith(sFile, TEXT(".wax")) || StringEndsWith(sFile, TEXT(".cda")) || StringEndsWith(sFile, TEXT(".mid")) || StringEndsWith(sFile, TEXT(".midi")) || \
  StringEndsWith(sFile, TEXT(".rmi")) || StringEndsWith(sFile, TEXT(".au")) || StringEndsWith(sFile, TEXT(".aac")))
  return TRUE;
 return FALSE;
} 

Function FileExtNotSupportedByPlugin returns false encountering any of the mentioned extension.

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle::CaptionAlreadyAvailable
// Check if SAMI caption is already available/loaded
// if an error occurs returns true, to avoid problems
// use the IWMPClosedCaption interface to retrieve info
BOOL CWMPNativeSubtitle::CaptionAlreadyAvailable() {
    CComPtr<IWMPClosedCaption> spWMPClosedCaption;

 HRESULT hr = E_FAIL;

 hr = m_spCore->get_closedCaption(&spWMPClosedCaption);
 if (FAILMSG(hr))
     return true;

    if (spWMPClosedCaption)
    {
  BSTR smiFileName;
  hr = spWMPClosedCaption->get_SAMIFileName(&smiFileName);
  if (FAILMSG(hr))
   return TRUE;

  if (smiFileName != NULL) {
   BOOL isNotEmpty = (BOOL) wcscmp(smiFileName, L"");
   ::SysFreeString(smiFileName);
   return isNotEmpty;
  }
    }
 return FALSE;
}

This function uses get_closedCaption method of COM interface IWMPCore. and get_SAMIFileName method of COM interface IWMPClosedCaption to check whether a SAMI caption has been loaded.

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle::EnableCaptionFromSubtitle
// Convert caption and load
// if this procedure is successful it returns true otherwise false
BOOL CWMPNativeSubtitle::EnableCaptionFromSubtitle() {
 // subtitles are available for conversion
 // currently we only support srt format
 LPTSTR subInputName = GetSubtitleFilePath();
 if (subInputName == NULL)
  return FALSE;

 // subInputName will be freed when SubToSAMIConverter object is destroyed
 SubToSAMIConverter subToSAMIConverter(subInputName);
 if (subToSAMIConverter.convert_to_sami()) {
  BSTR mediaFileName;
  HRESULT hr = m_spCore->get_URL(&mediaFileName);
  if (FAILMSG(hr))
   return FALSE;
  m_spCore->close();
  m_spCore->put_URL(mediaFileName);
  ::SysFreeString(mediaFileName);
  return TRUE;
 }
 return FALSE;
} 

Afterwards, we add function definitions in cpp file (WMPNativeSubtitle.cpp). We get srt subtitle file path in a pointer variable using function GetSubtitleFilePath.

Function GetSubtitleFilePath acquires the media file path and replaces its extension with srt. It also checks whether this final path exists. If file does not exist the function returns NULL.

After getting subtitle file path we pass it to a member function of class SubToSAMIConverter. We discuss about the conversion procedure on next section. This function returns true if conversion procedure is successful. On success we close the media file and reopen to load caption using put_URL method of IWMPCore interace.

SubRip to SAMI Conversion

Introduction

Function convert_to_sami which is a public member of class SubToSAMIConverter does required conversion from subrip to SAMI. To understand this conversion procedure we need knowledge on following topics:

  • SubRip - it is the input format for convert_to_sami
  • SAMI - it is the output format for convert_to_sami

You can visit Wikipedia's entry on SubRip for detail information. Basically, a subrip text file is a collection of structured caption text such as following:

  • A numeric number that increments by one for each caption
  • Time Stamp
  • Text

Additionally, SubRip supports some basic html formatting.

To know about SAMI (Microsoft Synchronized Accessible Media Interchange) format and its specification please go through Understanding SAMI 1.0 on msdn In brief, a SAMI file looks like a html document (though not really html) which contains caption texts inside body tag

  • A sync tag mentioning starting time in milliseconds
  • Caption text under a p tag mentioning class and id to apply required formatting

Before body tag formatting style classes and ids are defined for p tag. SAMI captioning has limited html support and it allows captions in multiple languages.

Conversion - Core Logic

When an object of the class SubToSAMIConverter is created passing the input subtitle file (.srt) path using constructor it creates following files:

  • Output SAMI Document(smi text file )
  • Log file (if specified), it helps debugging

Afterwards, convert_to_sami function does follow:

Writes initial style description and header to the smi file which looks like this:

<SAMI>
<head>
  <STYLE TYPE="text/css">
 <!-- 
  P {
   font-size:1.2em;
   font-family: Arial, Sans-Serif;
   font-weight: normal;
   color: #FFFFFF;
   background-color: transparent;
   text-align: center; }
   .SACAPTION { name: English; lang: EN-US; }
 -->
  </STYLE>
</head>
<body>  

Name of our paragraph tag class for english caption is 'SACAPTION'.Then it takes each line from srt file as input and recognizes line type. Line input is taken using following function,

BOOL SubToSAMIConverter::get_sub_line(LPTSTR *lineStr, int *length);   

and line type is determined by following function,

LINETYPE get_line_type(LPTSTR line, SubToSAMIConverter* pSamiConverter); 

Following types of lines are considered in an srt file:

  • New line - when a newline is encountered first we verify whether we got this newline after caption in that case w he n e
  • Number Counter - is named seqeuence in the program; nothing is written to output smi file when it is encountered. However, in future, we can do a checking whether number counter is correct and may write a warning to the log file when such event occurs.
  • TimeStamp - two timestamps are found in these lines in srt file considering standard is followed. Hence, we save starting and ending time and perform necessary logging.
  • Caption Text - when texts are found I do some checking such as whether timestamps and string pointer are valid. If everything is okay, program does following for each of the two timestamps.
    • for starting time stamp, text is printed with sync time, see following example,
       <SYNC Start="8705">
       <p class="SACAPTION">
        I'm so lonely, broken angel
       </p>
       </SYNC>   
    • for ending time stamp, a white space is written to file with sync time. Following example illustrates this:
       <SYNC Start="12178">
       <p class="SACAPTION">
        &nbsp;
       </p>
       </SYNC>
  • The reason behind this is that, Windows Media Player keeps displaying the same caption till next sync time is found. This can be irritating. Imagine: a short sentence from the speaker in the video, then other events can occur and next sentence might be uttered in a long time.
  • Default - any garbage: is considered as an error and should return failure. This is strict. Usually, this case is not satisfied.

Handling Text Encoding During File I/O

While reading from subtitle text files it should be considered that input text file can be encoded in following formats:

  • ANSI Text File
  • UTF-8 Text with BOM
  • UTF-8 Text without BOM
  • UTF-16 Text File (BOM is default)
  • UTF-32 Text File (BOM is default)

Where are encoding stuffs happening?

There are two functions where encoding is being taken cared of:

  • function read_data_into_buffer which is used/called by get_sub_line - reads BYTEs from file and converts them to Unicode buffers
  • writeSmiText and writeLog - writes texts by encoding them into utf-8.

get_sub_line function is giving us a string pointer to the line read from file and length of the line in an integer pointer variable using read_data_into_buffer read_data_into_buffer reads 1024 bytes from specified file using win32 ReadFile function (file handles for ReadFile and WriteFile were created in constructor of the class). W e

All text encoding related functions are defined in encoding.cpp and declared in encoding.h

enum TEXT_ENCODE_FORMAT { ANSI_TEXT, UTF8_TEXT_WITHOUT_BOM, UTF8_TEXT_WITH_BOM, UTF16_TEXT, UTF32_TEXT, UNKNOWN_TEXT_TYPE };

LPTSTR ConvertUTF8ToUTF16( __in LPCSTR pszTextUTF8 );
LPTSTR ConvertANSIToUTF16( __in LPCSTR pszTextANSI );
LPSTR ConvertUTF16ToUTF8( __in LPCWSTR pszTextUTF16 );
BOOL is_utf8_encoded(__in const unsigned char* inStr);
TEXT_ENCODE_FORMAT get_text_file_encoding(__in const unsigned char* inStr); 

Primarily function get_text_file_encoding is giving us type of encoding used by the input text file. Here's how code of this function looks like:

TEXT_ENCODE_FORMAT get_text_file_encoding(__in const unsigned char* inStr) {
 // UTF-32 detection
 // BOM is 00 00 FE FF (for BE) or FF FE 00 00 (for LE).
 BYTE firstByte = inStr[0];
 BYTE secondByte = inStr[1];
 BYTE thirdByte = inStr[2];
 // msdn Using Byte Order Marks ref: <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/dd374101(v=vs.85).aspx">http://msdn.microsoft.com/en-us/library/windows/desktop/dd374101(v=vs.85).aspx

 // Windows is LE
 // big endian and little endian
 if ((firstByte == 0x00 && secondByte == 0x00 && thirdByte == 0xFE && inStr[3] == 0xFF) || (firstByte == 0xFF && secondByte == 0xFE && thirdByte == 0x00 && inStr[3] == 0x00))
  return UTF32_TEXT;
 // BOM is FE FF (for BE) or FF FE (for LE). Note that the UTF-16LE BOM is found at the start of the UTF-32LE BOM, so check UTF-32 first.
 // There may be UTF-16 files without a BOM, but it would be really hard to detect them. The only reliable way to recognize UTF-16 without a BOM is to look for surrogate pairs (D[8-B]xx D[C-F]xx), but non-BMP characters are too rarely-used to make this approach practical.
 if ((firstByte == 0xFE && secondByte == 0xFF) || (firstByte == 0xFF && secondByte == 0xFE))
  return UTF16_TEXT;
 // The UTF-8 representation of the BOM is the byte sequence 0xEF,0xBB,0xBF
 // for windows notepad; double check of is_utf8_encoded, just in case
 if ((firstByte == 0xEF && secondByte == 0xBB && thirdByte == 0xBF) && is_utf8_encoded(&inStr[3]))
  return UTF8_TEXT_WITH_BOM;

 if (is_ANSI_encoded(inStr) == FALSE)
  return UTF8_TEXT_WITHOUT_BOM;

 return ANSI_TEXT;
}  

Input to this function, inStr is usually first 1024 bytes of data from the file.

Plugin Properties/Configuration Dialog Box

There are several issues to deal with while including a plugin configuration/property dialog box

  • Keeping up to date state in the plugin class
  • Persistently storing the settings applied by user
  • Properly updating the dialog for display with up to date settings

Our plugin property dialog is pretty sample and to demonstrate all the issues to deal successfully.

How is it implemented?

We maintain a single property. It is used to know whether logging should be enabled or not. At first we declare a variable as private member of class CWMPNativeSubtitle inside WMPNativeSubtitle.h

BOOL m_bLogSetting; 

This variable is used to keep up to date information about log setting. By default it is disabled and plugin will not create a log file along side creating an SMI file. Have a look at constructor,

CWMPNativeSubtitle::CWMPNativeSubtitle():
  m_sFilePath(NULL),
  m_bLogSetting(FALSE)
{ ... } 

For example, user has enabled logging using Properties Dialog Box of the plugin. In that case, it is read from Windows Registry in same constructor,

// read m_bLogSetting from registry
CRegKey key;
LONG    lResult;

// also consider whether this location kwszPrefsRegKey is readable/writeable
lResult = key.Open(HKEY_CURRENT_USER, kwszPrefsRegKey, KEY_READ);
if (ERROR_SUCCESS == lResult)
{
 DWORD   dwValue = 0;
 DWORD dwType = 0;
 ULONG uLength = sizeof(dwValue);
 lResult = key.QueryValue(kwszPrefsLogging, &dwType, &dwValue, &uLength);

 if (ERROR_SUCCESS == lResult)
 {
  m_bLogSetting = (BOOL) (dwValue & 0x0001);
 }
} 

As you can see for the first time plugin is enabled it will not be able read it from registry as relevant registry keys have not been created yet. In that case, Key Open procedure will fail and variable m_bLogSetting's disabled state will not be changed. Registry Key Path and Name is declared in header file (WMPNativeSubtitle.h)

// registry location for preferences, not sure whether this is the best location to store plugin properties
const WCHAR kwszPrefsRegKey[] = L"Software\\Microsoft\\MediaPlayer\\UIPlugins\\{52738E25-987F-4CA8-A674-5154267BF422}\\WmpNativeSubtitle";
const WCHAR kwszPrefsLogging[] = L"LogSettings"; 

This information needs to be propagated to the dialog class so that when user clicks they can see it in updated state. It is why OnInitDialog function of the dialog class retrieves it and updates check button,

if (m_pPlugin) {
 m_pPlugin->get_log_status(&bLogStatus);
 if (bLogStatus)
  SendDlgItemMessage(IDC_CHECK_LOG, BM_SETCHECK, BST_CHECKED, (int) bLogStatus);
 else
  SendDlgItemMessage(IDC_CHECK_LOG, BM_SETCHECK, BST_UNCHECKED, (int) bLogStatus);
}

There are two method implemented inside class CWMPNativeSubtitle to retrieve and update log status from other classes,

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle::get_log_status
//
// Property get to retrieve log status via the public interface.
/////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWMPNativeSubtitle::get_log_status(BOOL *pVal)
{
 if (NULL == pVal)
 {
  return E_POINTER;
 }
 *pVal = m_bLogSetting;
 return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle::set_log_status
//
// Property put to store the scale value via the public interface.
/////////////////////////////////////////////////////////////////////////////
STDMETHODIMP CWMPNativeSubtitle::set_log_status(BOOL newVal)
{
 m_bLogSetting = newVal;
 return S_OK;
}  

When user enables/disables logging from dialog box it is updated both in registry and CWMPNativeSubtitle class member,

LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hwndCtl, BOOL& fHandled)
{
 int logStatus = 0;
 UINT32 state = IsDlgButtonChecked(IDC_CHECK_LOG);

 if (state == BST_CHECKED)
 {
  logStatus = 1;
 }
 else
 {
  logStatus = 0;
 }
 // update registry
 CRegKey key;
 LONG    lResult;

 lResult = key.Create(HKEY_CURRENT_USER, kwszPrefsRegKey);
 if (ERROR_SUCCESS == lResult)
 {
  DWORD dwValue = (DWORD) logStatus;
  lResult = key.SetValue(kwszPrefsLogging, REG_DWORD, &dwValue, sizeof(dwValue));
 }

 // update plug-in class member
 if (m_pPlugin)
 {
  m_pPlugin->set_log_status(logStatus);
 }

 EndDialog( IDOK );
 return 0;
} 

Still, there is scope of improvement, such as, to:

  • Ensure registry write is done only when value has changed.
  • Eliminate unnecessary registry read when class member is already updated.

This can be important when you have a number of property members in the class.

How does the Plugin Support Other Languages and Unicode?

Our output SAMI files are encoded in utf-8. Hence, it can be speculated that writing Unicode characters for languages other than English should work and that Windows Media Player should by default be able to display them properly. However, this is only a speculation.

Please have a look at following caption record in an smi file,

<SYNC Start="272000">
<p class="SACAPTION">
 Long, long live the walls we crashed through<br>
 زنده.زنده باد دیوارهایی که در هم شکستیم
</p> 

When we load it with the relevant media file the rendering of caption looks like the one in screenshot,

Reason can be one of the two. One is that Windows Media Player does not really support utf-8. It only parses ANSI. In that case, when you split a 2 byte Unicode character into two you get two ANSI characters. Media Player is displaying those separated ANSI characters. Other reason can be that, probably we have missed something in documentation, merely putting Unicode chars is not probably the option. Probably, an expert in the field can shed light on it.

SAMI document has limited html support. If Unicode characters are encoded in html format Windows Media Player displays them correctly. Have a look at following caption record in the smi file,

<SYNC Start="272000">
<p class="SACAPTION">
 Long, long live the walls we crashed through<br>
 &#1586;&#1606;&#1583;&#1607;.&#1586;&#1606;&#1583;&#1607; &#1576;&#1575;&#1583; &#1583;&#1740;&#1608;&#1575;&#1585;&#1607;&#1575;&#1740;&#1740; &#1705;&#1607; &#1583;&#1585; &#1607;&#1605; &#1588;&#1705;&#1587;&#1578;&#1740;&#1605;
</p>
</SYNC>

Here's the screenshot when this smi is loaded with relevant media file,

1586, 1606, 1586 etc are the decimal Unicode value for the respective Arabic letter. Let's have a peek into the implementation in source file (SAMIConversion.cpp),

/////////////////////////////////////////////////////////////////////////////
// CWMPNativeSubtitle::EmbedUnicodeSymbols
// Take Unicode string to write into UTF-8 file
// if Unicode char is found encode in html, otherwise append as direct ansi char
void SubToSAMIConverter::EmbedUnicodeSymbols(LPWSTR pSubStr, LPWSTR pLine) {
 size_t pSubLen = wcslen(pSubStr);
 for (int i = 0; pLine[i] != _T('\0'); i++) {
  if ((unsigned int)pLine[i] <= 0xFF) {
   pSubStr[pSubLen++] = pLine[i];
  }
  else {
   pSubStr[pSubLen++] = L'&';
   pSubStr[pSubLen++] = L'#';

   WCHAR numbuf[12];
   _itow_s((int)pLine[i], numbuf, 10, 10);
   for (int j = 0; numbuf[j] != L'\0'; j++)
    pSubStr[pSubLen++] = numbuf[j];
   pSubStr[pSubLen++] = L';';
  }
 }
 pSubStr[pSubLen] = L'\0';
}

To optimize the plugin, we only call EmbedUnicodeSymbols function only when Unicode character has been encountered before. First string pointer in the parameters of the function is the one that we write to smi file later and second parameter string pointer is the one we found originally in subrip file and converted to Unicode string. So what it basically does, is that, the function,

  • Iterates till null terminating character of pLine is encountered
  • For each of the characters in pLine string it checks whether it is in ansi char range (ASCII code 0 to 255)
  • If the character is in range it is appended to the first string.
  • Otherwise, it appends '&#' Then appends decimal number found by converting the Unicode value of the character and finally appends a ';'

Features yet to implement

Here is a tentative list of features to implement,

  • Currently smi file is created in the same location of media file. If the directory is not writeable Plugin won' be able display caption. Solution is to change smi file location to a temporary directory which will always be writeable. This feature has been implemented and has been discussed on next part of the article.
  • Add option in properties dialog box to change font, color and other style of subtitle/caption (currently we are doing this by modifying registry manually)
  • Add option in properties dialog box to change caption height (currently we have to modify registry manually to achieve this)
  • Create Installer and release both 64 bit and 32 bit

Source and Builds

Acquiring Source

There are two ways to get the source code of the project.

64 bit builds

If you wish to use 64 bit plugin please set 64 bit Windows Media Player as default. You can find instructions on Microsoft Answers page on how to set 64 bit Windows Media Player as default. If you have downloaded source using attached zip please use the zip archive mentioning x64. Open the solution file with Visual Studio. After building the project we get an output dll file. msdn documentation suggests running plugin project using Visual Studio with administrator privilege. UI Plugin Project generates a dll file which has to be registered into the system using regsvr32 command that requires admin privilege. However, I built the project as regular user. Later, I applied the command manually using a command prompt with admin privilege. Note, you have to provide correct path of the dll file in the command,

regsvr32 "F:\Sourcecodes\Plugin-App\WMPNativeSubtitle\x64\Release\WMPNativeSubtitle_plugin_x64.dll"     

When it is successful a dialog box confirms it.

After that you have to ensure whether local caption is enabled in Windows Media Player. Please refer to section: Enabling Caption/Subtitle and follow all of the step to enable the plugin finally.

32 bit builds

Instructions for 32 builds are almost same as for 64 bit builds. Except, if you download source attached zip you must download the one mentioning x86. Note, if you acquire the source using code.google.com's project page you will find that there are two directories containing Visual Studio Project configuration files: one name x64 and other one is x86. x86 directory contains Visual Studio solution and project files for 32 bit. Copy this files and overwrite files in x64 which give you required files for 32 bit build.

After building dll from the code use regsvr32 command to register the dll. regsvr32 works for 32 bit dlls as well. Hence, registering the dll will be an easy work of apply a command with privilege elevated.

Enabling Caption/Subtitle

By default local captions are not enabled in Windows Media Player.

Step 1 - Turn Local Captions On

To enable captions to be shown from local source, you have to go to options,

Then navigate to "Security" tab and tick the check box saying "Show local captions when present" as displayed in screenshot below,

And click ok to save settings.

Step 2 - Enable English Caption

This step to enable default English caption, lyrics, subtitle is also important. Without enabling it captions won't be displayed.

At this stage subtitle/caption will be displayed if smi file is available with same name as of video file name. However, to enable captions for subrip (.srt) files we have to proceed to next step.

Step 3 - Enable WmpNativeSubtitle Plugin

Our final step is to enable the plugin. To do that, we have to navigate to Plug-Ins tab from More options. And then select "Background" from Category. Then, we must enable the plugin by checking the tick box on the left of the Plugin Name.

At this stage, With all these steps properfly followed Windows Media Player is now capable of displaying Captions for both .srt and .smi files.

Installing Plugin without building from Code

Please download proper release dll zip archive (x64 for 64 bit plugin and x86 for 32 bit plugin). A 64 Windows OS (except probably Server 2012) can run both 32 bit and 64 bit application. If you want to use the 64 bit plugin you must set Windows Media Player default following instruction from http://answers.microsoft.com/en-us/windows/forum/windows_7-windows_programs/making-windows-media-player-64-bit-default/bd4872b3-75e8-4d81-ae8a-df50798d5113

After downloading extract the zip archive. There are two dll files inside the extracted directory:

  • WMPNativeSubtitle_plugin.dll or WMPNativeSubtitle_plugin_x64.dll
  • msvcr120.dll

For example, if directory path is: F:\Plugin then following command will install/register the 64 bit dll into system (requires elevated/admin command prompt):

regsvr32 "F:\Plugin\WMPNativeSubtitle_plugin_x64.dll"      

To install the 32 bit plugin, modify the file name to exclude "_x64",

regsvr32 "F:\Plugin\WMPNativeSubtitle_plugin.dll"       

Finally, you must follow instructions from section Enabling Caption/Subtitle to enable caption.

Tips and Tricks

How to change caption height

Windows Media Players caption rendering system, you may note, is bit different. There is a darker background around caption text though transparency is enabled. Default caption height seems to be large. However, it is easy to change it. If you navigate to following registry path,

[HKEY_CURRENT_USER\Software\Microsoft\MediaPlayer\Player\Tasks\NowPlaying]
"CaptionsHeight"=dword:00000064  

There is a key named CaptionsHeight under it. 0x64 is large. I use hexadecimal value 0x48, which is comfortable for me.

Conclusion

While there are plenty of audio/video player projects around there is appeal of Windows Media Player to some people. It's clean and simple; coming with the OS this software is still classic favorite to many. While Windows Media Player supports many of the latest video file formats and encoding it provides better hardware acceleration and clarity having advantage of the support from the OS. I hope this article will be useful to fans of Windows Media Player.

Points of Interest

Any kind of suggestion, review to improve this article are welcome. While developing the plugin to make an optimal solution and to implement some components from scratch you might notice I have reinvented some of the wheels (whether this has been better or worse, experts can tell).

Side-note: I created this application almost 8 months ago. I am sorry that I am late to reproduce it here. Sharing is caring. There are other sides to the original app but they should be in different topic, in different article.

I should also mention that while typing this into code-project editor I found a few irritating bugs of the editor such as unnecessary white space coming automatically at end of lines and crash of web-page on Internet Explorer 11 while copy pasting some elements in editor (later, when I reopened the article editor I found almost 700 lines of same content!). Had the technical team have looked into it could make our future experience better.

Thanks everyone.

History

October 2013 - Plug-In developed

May 8, 2014 - article, first revision

June 16, 2014 - article, second revision

June 21, 2014 - article, third revision

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share

About the Author

Md Atiqur Rahman (Atique)
Software Developer Reve Systems
Bangladesh Bangladesh
Interested in programming and english literature.
Follow on   Twitter

Comments and Discussions

 
QuestionNice work PinmemberMember 1101074713-Aug-14 5:39 
QuestionIdea for you ... PinmemberThe_Inventor19-Jun-14 16:39 
QuestionSubtitles Files PinmemberTSchind16-Jun-14 7:56 
AnswerRe: Subtitles Files PinmemberMd Atiqur Rahman (Atique)16-Jun-14 16:08 
GeneralRe: Subtitles Files PinmemberTSchind16-Jun-14 21:51 
GeneralRe: Subtitles Files PinmemberMd Atiqur Rahman (Atique)17-Jun-14 1:25 
GeneralRe: Subtitles Files PinmemberTSchind17-Jun-14 2:23 
QuestionThank you PinmemberTSchind16-Jun-14 7:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140821.2 | Last Updated 7 Jul 2014
Article Copyright 2014 by Md Atiqur Rahman (Atique)
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid