Updated 2017-06-14 22:26:20 by bll

VLC Tcl Extension edit

bll 2016-12-28 : One full day's work. This is a Tcl interface to libvlc, which comes with the VLC media player. The tighter interface gives better control over song position, time position and player state.

I only need audio, and don't use playlists. The media sub-command only handles file paths right now. There's quite a bit that could still be implemented. But most of that implementation would be fairly straightforward.

I have given up trying to get callbacks working. Calling Tcl code from the VLC event handler does not seem to work properly on Windows.

2017-1-3: Fix for XP and Vista

2017-6-12: Fixed exit callback; Added commands to get device list and set device.

I have the dynamic libraries built for VLC 2.1.x and 2.2.x for Windows 32/64, Linux 32/64 and Mac OS X if you want a pre-built copy.

JJM 2017-01-03: It might be nice to put this in a Fossil repository someplace? It looks quite useful.

bll 2017-1-3: Feel free. It's a little basic at the moment as I only need audio, but adding the non-file media type, playlist management and video shouldn't be difficult. It is working well -- I have been using the telnet interface to VLC for the last five years, and this is rather better.

DG 17-03-10 Super SWEET! Just what I was looking for :)

test.tcl
#!/usr/bin/tclsh

set os $::tcl_platform(os)
if { [regexp -nocase {^win} $os] } {
  set os Windows
}

load [pwd]/$os/tclvlc64[info sharedlibextension]
tclvlc init --intf dummy --no-video --ignore-config \
    --no-media-library --no-playlist-autostart \
    --no-loop --no-random --no-repeat --quiet --play-and-stop \
    --audio-filter scaletempo

if { $os eq "Linux" } {
  tclvlc media /home/bll/sources/ballroomdj/test.dir/test-files/counter.mp3
}
if { $os eq "Darwin" } {
  tclvlc media /Users/bll/Desktop/BallroomDJ.app/Contents/MacOS/test.dir/test-files/counter.mp3
}
if { $os eq "Windows" } {
  tclvlc media [file nativename {C:/Users/bll/Desktop/BallroomDJ/test.dir/test-files/counter.mp3}]
}

tclvlc play
set state [tclvlc state]
while { $state eq "opening" || $state eq "buffering" } {
  after 100
  set state [tclvlc state]
}
tclvlc seek 0.0387
set ::x 0
after 1000 set ::x 1
vwait ::x
tclvlc close
exit

tclvlc.c
/*
 * Copyright 2016-2017 Brad Lanam Walnut Creek CA US
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tcl.h>
#include <vlc/vlc.h>
#include <vlc/libvlc_version.h>
#ifdef COMP_WINDOWS
# include <windows.h>
#endif

typedef struct { char *name; Tcl_ObjCmdProc *proc; } EnsembleData;

typedef struct {
  libvlc_state_t        state;
  const char *          name;
} stateMap_t;

static const stateMap_t stateMap[] = {
  { libvlc_NothingSpecial,  "idle" },
  { libvlc_Opening,         "opening" },
  { libvlc_Buffering,       "buffering" },
  { libvlc_Playing,         "playing" },
  { libvlc_Paused,          "paused" },
  { libvlc_Stopped,         "stopped" },
  { libvlc_Ended,           "ended" },
  { libvlc_Error,           "error" }
};
#define stateMapMax (sizeof(stateMap)/sizeof(stateMap_t))

typedef struct {
  Tcl_Interp            *interp;
  libvlc_instance_t     *inst;
  char                  version [40];
  libvlc_media_player_t *mp;
  libvlc_state_t        state;
  int                   argc;
  const char            **argv;
  const char            *device;
} vlcData_t;

const char *
stateToStr (
  libvlc_state_t    state
  )
{
  int        i;
  const char *tptr;

  tptr = "";
  for (i = 0; i < stateMapMax; ++i) {
    if (state == stateMap[i].state) {
      tptr = stateMap[i].name;
      break;
    }
  }
  return tptr;
}

libvlc_state_t
stateToValue (
  char *name
  )
{
  int   i;
  libvlc_state_t state;

  state = libvlc_NothingSpecial;
  for (i = 0; i < stateMapMax; ++i) {
    if (strcmp (name, stateMap[i].name) == 0) {
      state = stateMap[i].state;
      break;
    }
  }
  return state;
}

void
vlcEventHandler (
  const struct libvlc_event_t *event,
  void *cd
  )
{
  vlcData_t     *vlcData = (vlcData_t *) cd;
  int           objc;
  Tcl_Obj       **objv;
  Tcl_Obj       *tobj;

  if (event->type == libvlc_MediaStateChanged) {
    vlcData->state = event->u.media_state_changed.new_state;
  }
}

int
vlcDurationCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int               rc;
  libvlc_time_t     tm;
  double            dtm;
  vlcData_t *vlcData = (vlcData_t *) cd;

  if (objc != 1) {
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    tm = libvlc_media_player_get_length (vlcData->mp);
    dtm = (double) tm / 1000.0;
    Tcl_SetObjResult (interp, Tcl_NewDoubleObj (dtm));
  }
  return rc;
}

int
vlcGetTimeCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int       rc;
  libvlc_time_t     tm;
  double            dtm;
  vlcData_t *vlcData = (vlcData_t *) cd;

  if (objc != 1) {
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    tm = libvlc_media_player_get_time (vlcData->mp);
    dtm = (double) tm / 1000.0;
    Tcl_SetObjResult (interp, Tcl_NewDoubleObj (dtm));
  }
  return rc;
}

int
vlcIsPlayCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int       rc;
  int       rval;
  vlcData_t *vlcData = (vlcData_t *) cd;

  if (objc != 1) {
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  rval = 0;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    /*
     * In order to match the implementation of VLC's internal
     * isplaying command, return true if the player is paused
     */
    if (vlcData->state == libvlc_Opening ||
        vlcData->state == libvlc_Buffering ||
        vlcData->state == libvlc_Playing ||
        vlcData->state == libvlc_Paused) {
      rval = 1;
    }
    Tcl_SetObjResult (interp, Tcl_NewIntObj (rval));
  }
  return rc;
}

int
vlcMediaCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int               rc;
  libvlc_media_t    *m;
  libvlc_event_manager_t    *em;
  vlcData_t         *vlcData = (vlcData_t *) cd;
  char              *fn;
  struct stat       statinfo;

  if (objc != 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "mediapath");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    fn = Tcl_GetString(objv[1]);
    if (stat (fn, &statinfo) != 0) {
      rc = TCL_ERROR;
      return rc;
    }
    m = libvlc_media_new_path (vlcData->inst, fn);
    libvlc_media_player_set_rate (vlcData->mp, 1.0);
    em = libvlc_media_event_manager (m);
    libvlc_event_attach (em, libvlc_MediaStateChanged,
        &vlcEventHandler, vlcData);
    libvlc_media_player_set_media (vlcData->mp, m);
    /* on mac os x, the device has to be set after the media is set */
    if (vlcData->device != NULL) {
      libvlc_audio_output_device_set (vlcData->mp, NULL, vlcData->device);
    }
    libvlc_media_release (m);
  }
  return rc;
}

int
vlcPauseCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int   rc;
  vlcData_t     *vlcData = (vlcData_t *) cd;

  if (objc != 1) {
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    if (vlcData->state == libvlc_Opening ||
        vlcData->state == libvlc_Buffering) {
      ;
    } else if (vlcData->state == libvlc_Playing) {
      libvlc_media_player_set_pause (vlcData->mp, 1);
    } else if (vlcData->state == libvlc_Paused) {
      libvlc_media_player_set_pause (vlcData->mp, 0);
    }
  }
  return rc;
}

int
vlcPlayCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int   rc;
  vlcData_t     *vlcData = (vlcData_t *) cd;

  if (objc != 1) {
    Tcl_WrongNumArgs(interp, 1, objv, "");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    libvlc_media_player_play (vlcData->mp);
  }
  return rc;
}

int
vlcRateCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int       rc;
  vlcData_t *vlcData = (vlcData_t *) cd;
  float     rate;
  double    d;


  if (objc != 1 && objc != 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "?rate?");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    if (objc == 2 && vlcData->state == libvlc_Playing) {
      rc = Tcl_GetDoubleFromObj (interp, objv[1], &d);
      if (rc == TCL_OK) {
        rate = (float) d;
        libvlc_media_player_set_rate (vlcData->mp, rate);
      }
    }

    rate = libvlc_media_player_get_rate (vlcData->mp);
    Tcl_SetObjResult (interp, Tcl_NewDoubleObj ((double) rate));
  }
  return rc;
}

int
vlcSeekCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int               rc;
  libvlc_time_t     tm;
  double            dtm;
  vlcData_t         *vlcData = (vlcData_t *) cd;
  float             pos;
  double            d;


  if (objc != 1 && objc != 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "?position?");
    return TCL_ERROR;
  }

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    if (objc == 2 && vlcData->state == libvlc_Playing) {
      tm = libvlc_media_player_get_length (vlcData->mp);
      dtm = (double) tm / 1000.0;
      rc = Tcl_GetDoubleFromObj (interp, objv[1], &d);
      d = d / dtm;
      if (rc == TCL_OK) {
        pos = (float) d;
        libvlc_media_player_set_position (vlcData->mp, pos);
      }
    }
    pos = libvlc_media_player_get_position (vlcData->mp);
    Tcl_SetObjResult (interp, Tcl_NewDoubleObj ((double) pos));
  }
  return rc;
}

int
vlcStateCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int               rc;
  libvlc_state_t    plstate;
  vlcData_t         *vlcData = (vlcData_t *) cd;

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    plstate = vlcData->state;
    Tcl_SetObjResult (interp, Tcl_NewStringObj (stateToStr(plstate), -1));
  }
  return rc;
}

int
vlcStopCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int   rc;
  vlcData_t     *vlcData = (vlcData_t *) cd;

  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    libvlc_media_player_stop (vlcData->mp);
  }
  return rc;
}

int
vlcHaveAudioDevListCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  int       rc;

  rc = 0;
#if LIBVLC_VERSION_INT >= LIBVLC_VERSION(2,2,0,0)
  rc = 1;
#endif
  Tcl_SetObjResult (interp, Tcl_NewBooleanObj (rc));
  return TCL_OK;
}

#if LIBVLC_VERSION_INT >= LIBVLC_VERSION(2,2,0,0)
int
vlcAudioDevSetCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  vlcData_t     *vlcData = (vlcData_t *) cd;
  int           rc;
  char          *dev;

  if (objc != 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "deviceid");
    return TCL_ERROR;
  }
  rc = TCL_OK;
  if (vlcData->inst == NULL || vlcData->mp == NULL) {
    rc = TCL_ERROR;
  } else {
    dev = Tcl_GetString(objv[1]);
    if (vlcData->device != NULL) {
      free ((void *) vlcData->device);
    }
    vlcData->device = NULL;
    if (strlen (dev) > 0) {
      vlcData->device = strdup (dev);
    }
  }

  return rc;
}

int
vlcAudioDevListCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  vlcData_t                     *vlcData = (vlcData_t *) cd;
  Tcl_Obj                       *lobj;
  Tcl_Obj                       *sobj;
  libvlc_audio_output_device_t  *adevlist;
  libvlc_audio_output_device_t  *adevlistptr;

  if (vlcData->inst == NULL || vlcData->mp == NULL ||
      strcmp (vlcData->version, "2.2.0") < 0) {
    return TCL_ERROR;
  }

  lobj = Tcl_NewListObj (0, NULL);
  adevlist = libvlc_audio_output_device_enum (vlcData->mp);
  adevlistptr = adevlist;
  while (adevlistptr != (libvlc_audio_output_device_t *) NULL) {
    sobj = Tcl_NewStringObj (adevlistptr->psz_device, -1);
    Tcl_ListObjAppendElement (interp, lobj, sobj);
    sobj = Tcl_NewStringObj (adevlistptr->psz_description, -1);
    Tcl_ListObjAppendElement (interp, lobj, sobj);
    adevlistptr = adevlistptr->p_next;
  }
  libvlc_audio_output_device_list_release (adevlist);
  Tcl_SetObjResult (interp, lobj);
  return TCL_OK;
}
#endif

int
vlcVersionCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  vlcData_t     *vlcData = (vlcData_t *) cd;

  Tcl_SetObjResult (interp, Tcl_NewStringObj (vlcData->version, -1));
  return TCL_OK;
}

void
vlcClose (
  vlcData_t     *vlcData
  )
{
  int   i;

  if (vlcData->mp != NULL) {
    libvlc_media_player_stop (vlcData->mp);
    libvlc_media_player_release (vlcData->mp);
    vlcData->mp = NULL;
  }
  if (vlcData->inst != NULL) {
    libvlc_release (vlcData->inst);
    vlcData->inst = NULL;
  }
  if (vlcData->argv != NULL) {
    for (i = 0; i < vlcData->argc; ++i) {
      ckfree (vlcData->argv[i]);
    }
    ckfree (vlcData->argv);
    vlcData->argv = NULL;
  }
  if (vlcData->device != NULL) {
    free ((void *) vlcData->device);
    vlcData->device = NULL;
  }

  vlcData->state = libvlc_NothingSpecial;
}

void
vlcExitHandler (
  void *cd
  )
{
  vlcData_t     *vlcData = (vlcData_t *) cd;

  vlcClose (vlcData);
  ckfree (cd);
}

int
vlcReleaseCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  vlcData_t     *vlcData = (vlcData_t *) cd;

  vlcClose (vlcData);
  return TCL_OK;
}

int
vlcInitCmd (
  ClientData cd,
  Tcl_Interp* interp,
  int objc,
  Tcl_Obj * const objv[]
  )
{
  char          *tptr;
  char          *nptr;
  int           rc;
  int           i;
  int           len;
  vlcData_t     *vlcData = (vlcData_t *) cd;

  vlcData->argv = (const char **) ckalloc (sizeof(const char *) * (size_t) (objc + 1));
  for (i = 0; i < objc; ++i) {
    tptr = Tcl_GetStringFromObj (objv[i], &len);
    nptr = (char *) ckalloc (len+1);
    strcpy (nptr, tptr);
    vlcData->argv[i] = nptr;
  }
  vlcData->argc = objc;
  vlcData->argv[objc] = NULL;

  rc = TCL_ERROR;

  strcpy (vlcData->version, libvlc_get_version ());
  tptr = strchr (vlcData->version, ' ');
  if (tptr != NULL) {
    *tptr = '\0';
  }

  if (vlcData->inst == NULL) {
    vlcData->inst = libvlc_new (objc, vlcData->argv);
  }
  if (vlcData->inst != NULL && vlcData->mp == NULL) {
    vlcData->mp = libvlc_media_player_new (vlcData->inst);
  }
  if (vlcData->inst != NULL && vlcData->mp != NULL) {
    rc = TCL_OK;
  }

  return rc;
}

static const EnsembleData vlcCmdMap[] = {
#if LIBVLC_VERSION_INT >= LIBVLC_VERSION(2,2,0,0)
  { "audiodevlist", vlcAudioDevListCmd },
  { "audiodevset",  vlcAudioDevSetCmd },
#endif
  { "close",        vlcReleaseCmd },
  { "duration",     vlcDurationCmd },
  { "gettime",      vlcGetTimeCmd },
  { "init",         vlcInitCmd },
  { "haveaudiodevlist", vlcHaveAudioDevListCmd },
  { "isplay",       vlcIsPlayCmd },
  { "media",        vlcMediaCmd },
  { "pause",        vlcPauseCmd },
  { "play",         vlcPlayCmd },
  { "rate",         vlcRateCmd },
  { "seek",         vlcSeekCmd },
  { "state",        vlcStateCmd },
  { "stop",         vlcStopCmd },
  { "version",      vlcVersionCmd },
  { NULL, NULL }
};

int
Tclvlc_Init (Tcl_Interp *interp)
{
  Tcl_Namespace *nsPtr = NULL;
  Tcl_Command   ensemble = NULL;
  Tcl_Obj       *dictObj = NULL;
  Tcl_DString   ds;
  vlcData_t     *vlcData;
  int           i;
  int           rc;
  const char    *nsName = "::tcl::tclvlc";
  const char    *cmdName = nsName + 5;

#ifdef USE_TCL_STUBS
  if (!Tcl_InitStubs (interp,"8.3",0)) {
    return TCL_ERROR;
  }
#else
  if (!Tcl_PkgRequire (interp,"Tcl","8.3",0)) {
    return TCL_ERROR;
  }
#endif

#ifdef COMP_WINDOWS
  /* https://forum.videolan.org/viewtopic.php?t=135009 */
  {
    OSVERSIONINFO osvi;
    memset (&osvi, 0, sizeof(OSVERSIONINFO));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    GetVersionEx (&osvi);
    if ((osvi.dwMajorVersion < 6) ||
        (osvi.dwMajorVersion == 6) && (osvi.dwMinorVersion == 0)) {
      SetErrorMode(SEM_FAILCRITICALERRORS);
    }
  }
#endif
  vlcData = (vlcData_t *) ckalloc (sizeof (vlcData_t));
  vlcData->interp = interp;
  vlcData->inst = NULL;
  vlcData->mp = NULL;
  vlcData->argv = NULL;
  vlcData->state = libvlc_NothingSpecial;
  vlcData->device = NULL;
  Tcl_CreateExitHandler (vlcExitHandler, vlcData);

  nsPtr = Tcl_FindNamespace(interp, nsName, NULL, 0);
  if (nsPtr == NULL) {
    nsPtr = Tcl_CreateNamespace(interp, nsName, NULL, 0);
    if (nsPtr == NULL) {
      Tcl_Panic ("failed to create namespace: %s\n", nsName);
    }
  }

  ensemble = Tcl_CreateEnsemble(interp, cmdName, nsPtr, TCL_ENSEMBLE_PREFIX);
  if (ensemble == NULL) {
    Tcl_Panic ("failed to create ensemble: %s\n", cmdName);
  }
  Tcl_DStringInit (&ds);
  Tcl_DStringAppend (&ds, nsName, -1);

  dictObj = Tcl_NewObj();
  for (i = 0; vlcCmdMap[i].name != NULL; ++i) {
    Tcl_Obj *nameObj;
    Tcl_Obj *fqdnObj;

    nameObj = Tcl_NewStringObj (vlcCmdMap[i].name, -1);
    fqdnObj = Tcl_NewStringObj (Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
    Tcl_AppendStringsToObj (fqdnObj, "::", vlcCmdMap[i].name, NULL);
    Tcl_DictObjPut (NULL, dictObj, nameObj, fqdnObj);
    if (vlcCmdMap[i].proc) {
      Tcl_CreateObjCommand (interp, Tcl_GetString (fqdnObj),
           vlcCmdMap[i].proc, (ClientData) vlcData, NULL);
    }
  }

  if (ensemble) {
    Tcl_SetEnsembleMappingDict (interp, ensemble, dictObj);
  }

  Tcl_DStringFree(&ds);

  Tcl_PkgProvide (interp, cmdName+2, "0.1");
  return TCL_OK;
}

mkvlc.sh
#!/bin/sh

tv=8.6
slext=.so
os=`uname -s`
arch=`uname -m`
bits=64
case $arch in
  i?86*)
    bits=32
    ;;
esac
inc=-I/usr/include/tcl${tv}
lib=
lv=$tv
if [ $os = "Darwin" ]; then
  slext=.dylib
  # for MacPorts tcl
  inc=-I/opt/local/include
  lib=-L/opt/local/lib
  inc+=" -I/Applications/VLC.app/Contents/MacOS/include"
  lib+=" -L/Applications/VLC.app/Contents/MacOS/lib"
fi

${CC:-cc} -O -shared -fPIC -o tclvlc${slext} $inc -DUSE_TCL_STUBS tclvlc.c $lib -ltclstub${lv} -lvlc
test -d $os || mkdir $os
if [ -f tclvlc${slext} ]; then
  echo "tclvlc success"
  mv -f tclvlc${slext} $os/tclvlc${bits}${slext}
fi
if [ $os = "Darwin" ]; then
  install_name_tool -change "@loader_path/lib/libvlc.5.dylib" "/Applications/VLC.app/Contents/MacOS/lib/libvlc.5.dylib" Darwin/*
fi

winmkvlc.sh
#!/bin/bash

test -d Windows || mkdir Windows

case $MSYSTEM in
  *32)
    gcc -m32 -shared -static-libgcc -DCOMP_WINDOWS -o Windows/tclvlc32.dll \
        -I'/home/bll/vlc/vlc-2.2.4/include' \
        '-Wl,-rpath=/c/Program Files (x86)/VideoLAN/vlc' \
        -I$HOME/local-32/include -DUSE_TCL_STUBS tclvlc.c \
        -L$HOME/local-32/lib -ltclstub86 \
        -L'/c/Program Files (x86)/VideoLAN/vlc' -lvlc
    ;;
  *64)
    gcc -m64 -shared -static-libgcc -DCOMP_WINDOWS -o Windows/tclvlc64.dll \
        -I'/home/bll/vlc/vlc-2.2.4/include' \
        '-Wl,-rpath=/c/Program Files/VideoLAN/vlc' \
        -I$HOME/local-64/include -DUSE_TCL_STUBS tclvlc.c \
        -L$HOME/local-64/lib -ltclstub86 \
        -L'/c/Program Files/VideoLAN/vlc' -lvlc
    ;;
esac