Wikia

Vim Tips Wiki

Vim vlc controller and lyric synchronizer

Talk0
1,612pages on
this wiki
Revision as of 10:40, January 31, 2014 by JohnBot (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Proposed tip Please edit this page to improve it, or add your comments below (do not use the discussion page).

Please use new tips to discuss whether this page should be a permanent tip, or whether it should be merged to an existing tip.
created August 23, 2013 · complexity basic · author Vimsical · version 7.0

You can use Vim to control VLC and edit song lyrics or audio transcripts:

  • You can add audio timing information to each line of text transcript.
  • You can cue VLC audio to the time/line you are editing.

You will need:

  • Vim to edit text.
  • Perl to connect gvim to vlc via a socket.
  • VLC.

Below: Modify the path to Perl and Vim script according to the directory you save them in.

Setup vlc to listen on a socketEdit

"   VLC setup: $ vlc > tools > prefs > main interface > enable RC,
"       Tcp-Input=[localhost:2150]  .. ~/vim/vlc.pl will open this socket.
"       Console: [donot_open] console

Vim setupEdit

"   VIM Setup:
"     c:\> vim c:/atmp/happy.lrc
"     :so %
"     and cline is '[0:23.10] file:///C:/atmp/happy.mp3' .. press F9 to play from 23s
"   Music player key:
"     [F2] Play
"     [F3] Stop
"     [F4] Pause toggle
"   Lyric editor keys:
"     [F9]  Set filename/time from cline to vlc, or get filename/time to cline.
"     [F10] Get file from vlc into cline.
"     [F11] Get Time from vlc into cline.
" Notes:
"     To debug: change 'perl ~/vim/vlc.pl' to 'vimrun perl ~/vim/vlc.pl -debug'
" Testing:
"     Tested gvim73,cygwin perl 5, cygwin,vlc 2, on XP-PRO SP4.
"     1. file:///c:/atmp/happy.mp3 .. press F9 play this file in vlc.
"     2. [0:1:4] file:///c:/atmp/happy.mp3 .. press F9 play this file from 64 seconds.
"     3. 0:1:5 .. press F9 to cue/seek vlc to 65 second.
"     4. press F9, to add [time][filename playing in vlc] to this line.
" TODO:
"     o Test Audio filenames with spaces, unicode characters
"     o Test on Linux
" ==============================================================================

nmap <F2>   :call system("perl ~/vim/vlc.pl -cmd=play")<CR>
nmap <F3>   :call system("perl ~/vim/vlc.pl -cmd=stop")<CR>
nmap <F4>   :call system("perl ~/vim/vlc.pl -cmd=pause")<CR>

nmap <F9>   :call Vlc_Set_File_Time()<CR>
nmap <F10>  :call Vlc_Get_Filename()<CR>
nmap <F11>  :call Vlc_Get_Time()<CR>

Source vlc.vimEdit

" $Header: c:/cvs/repo/mosh/vim/vlc.vim,v 1.1 2013-08-26 17:04:52 a Exp $
" What: VLC remote control, for editing synchronizing lyrics.
" How:  Remote control vlc via tcp socket.
" AUTHOR: GPL(C) moshahmed/at/gmail
" ==============================================================================
function! Vlc_Set_File_Time()

  " Look for filename on cline
  let l:matcher_file = matchlist(getline('.'),'\v<(file:///.*[.]\w+)>')
  let l:absfile = ''
  if len(l:matcher_file) > 4
    call system("perl ~/vim/vlc.pl -cmd=set_file=" . l:matcher_file[1])
  else
    " Look for cfile (filename under cursor) in PWD
    let l:cfile = expand("<cfile>")
    " let l:absfile = expand("<cfile>:p")
    " if len(l:absfile) > 1 && filereadable('"'.l:absfile.'"')
    " endif
    if len(l:cfile) > 4 && filereadable(expand(l:cfile))
      let l:absfile = getcwd()."/".l:cfile
      if filereadable(l:absfile)
        call system("perl ~/vim/vlc.pl -cmd=set_file=file:///".l:absfile)
      else
        echo "Cannot read ".l:absfile
        return
      endif
    endif
  endif

  " Look for timestamp on cline, eg. [10] for 10 seconds, [1:1] for 61 seconds.
  let l:matcher_time = matchlist(getline('.'),'\v\[(\d.*)\]')
  if len(l:matcher_time) > 0
    call system( "perl ~/vim/vlc.pl -cmd=set_time=" . l:matcher_time[1])
  else
    " Look for timestamp colon without brackets on cline, eg. 1:2 for 62 seconds.
    let l:matcher_time = matchlist(getline('.'),'\v(\d+:[0-9:.]+)')
    if len(l:matcher_time) > 0
      call system("perl ~/vim/vlc.pl -cmd=set_time=" . l:matcher_time[1])
    endif
  endif

  " If neither filename or time on cline, insert them.
  if len(l:matcher_time) < 1 && len(l:matcher_file) < 1 && !filereadable(l:absfile)
    call Vlc_Get_Filename()
    call Vlc_Get_Time()
  endif
endfunction

" ==============================================================================
function! Vlc_Get_Filename()
  let l:fileis=system("perl ~/vim/vlc.pl -cmd=get_file")
  if  len(l:fileis) > 0
    " relfile=$(basename filename), insert shorter relative path if possible.
    " TODO: quote filenames; s/// fails if filename has commas, etc.
    let l:relfile=substitute(l:fileis, '^.*/', '', '')
    if filereadable(l:relfile)
      exe ':s,^, '.l:relfile.','
    else
      exe ':s,^,'.l:fileis.','
    endif
  endif
endfunction

" ==============================================================================
function! Vlc_Get_Time()
  let l:timeis=system("perl ~/vim/vlc.pl -cmd=get_time")
  if  len(l:timeis) > 0
    let l:timeis=substitute(l:timeis, '[^0-9:.]', '', 'g')
    let l:timeis='['.l:timeis.']'
    exe ':s/^/'.l:timeis.'/'
    " TODO: try this:
    " :put ='"'.l:timeis.'"'
  endif
endfunction
" ==============================================================================

~/vim/vlc.plEdit

#!/usr/bin/perl -w
# telnet.pl from perlmonks, modified to work with vlc remote control.
# AUTHOR: GPL(C) moshahmed/at/gmail 2013-08-22
# $Id: vlc.pl,v 1.1 2013-08-26 16:44:44 a Exp $
use strict;
use IO::Socket;
my ( $host, $port, $kidpid, $handle, $line );
( $host, $port ) = ('127.0.0.1', 2150);
my ($verbose,$debug,$cmd,$value,$linein) = (0,0,'',,'');

my $USAGE=q{
USAGE: $0 [options] -cmd=VLC_CMDS
WHAT: control vlc from vim
Options:
    -port=NN  .. default ($port)
    -h      .. help.
    -v      .. verbose
    -debug  .. to debug, also use ./telnet.pl to communicate with vlc
VLC_CMDS:
    play, pause, pause (toggle), stop,
    get_length .. in h:m:seconds
    get_time   .. in h:m:seconds
    get_file   .. gets file:///path
    set_time=1:2:3.40  .. for 1hour 2minutes 3seconds (integral seconds only)
    set_file=file:///c:/tmp/happy.mp3
};

# Process args
while( $_ = $ARGV[0], defined($_) && m/^-/ ){ shift; last if /^--$/; if(0){
    }elsif( m/^-[h?]$/ ){ die $USAGE;
    }elsif( m/^-v$/    ){ $verbose++;
    }elsif( m/^-debug$/    ){ $debug++;
    }elsif( m/^-port=(\d+)$/ ){ $port = $1;
    }elsif( m/^-cmd=(set_.+)=(.+)$/ ){ $cmd = $1; $value = $2;
      $value = hms_to_seconds($value);
      warn "$cmd=$value\n" if $debug;
    }elsif( m/^-cmd=(.+)$/ ){ $cmd = $1;
    }elsif( m/^-linein$/ ){ $linein=1; # read stdin (unused).
    }else{ die $USAGE,"Unknown option '$_'\n"; }
}

# foreach(@ARGV) { } .. process leftover args (unused).

# create a tcp connection to the specified host and port
$handle = IO::Socket::INET->new(
    Proto    => "tcp", PeerAddr => $host, PeerPort => $port
  ) or die "can't connect to port $port on $host: $!";
  $handle->autoflush(1);    # so output gets there right away
print STDERR "[Connected to $host:$port]\n" if $verbose;

# split the program into two processes, identical twins
die "can't fork: $!" unless defined( $kidpid = fork() );

if ($kidpid) { # In parent, parse reply from vlc.
    # the if{} part runs only in the parent process
    DONE:
    while ( defined( $line = <$handle> ) ) {
      if    ($cmd eq 'get_length') {
        if( $line =~ m,^\d+,) {
          print STDOUT seconds_to_hms($line);
          last DONE;
        }
      }elsif($cmd eq 'get_time') {
        if( $line =~ m,^\d+,) {
          print STDOUT seconds_to_hms($line);
          last DONE;
        }
      }elsif($cmd eq 'get_file') {
        if( $line =~ m,(file://.*)\s[()],i ) {
          my $filename = $1;
          print STDOUT $filename;
          last DONE;
        }
      }elsif($cmd eq 'status') {
        print STDOUT $line;
        last DONE if $line =~ m,returned,;
      }elsif($cmd eq 'play') { last DONE;
      }elsif($cmd eq 'pause') { last DONE;
      }elsif($cmd eq 'stop') { last DONE;
      }else{ print STDOUT $line;
        last DONE if $line =~ m,returned,;
      }
    } # DONE
    kill( "TERM", $kidpid );    # send SIGTERM to child
    exit 0;
} else {  # In child? send cmd to vlc
    if(     $cmd eq 'status' ){ print $handle "$cmd\n";
    }elsif( $cmd eq 'play' ){ print $handle "$cmd\n";
    }elsif( $cmd eq 'pause' ){ print $handle "$cmd\n";
    }elsif( $cmd eq 'stop' ){ print $handle "$cmd\n";
    }elsif( $cmd eq 'get_time' ){ print $handle "$cmd\n";
    }elsif( $cmd eq 'get_file' ){ print $handle
        "pause\nstatus\npause\nstatus\n";
    }elsif( $cmd eq 'get_length' ){ print $handle "play\nget_length\n";
    }elsif( $cmd eq 'set_time' ){ print $handle "seek $value\n";
    }elsif( $cmd eq 'set_file' ){ print $handle "clear\nadd $value\n";
    }else{                        print $handle "$cmd\n";
    }
    exit 0;
}

sub seconds_to_hms {
  my $hourz=int($_[0]/3600);
  my $leftover=$_[0] % 3600;
  my $minz=int($leftover/60);
  my $secz=int($leftover % 60);
  if ($hourz > 0){
    return sprintf ("%d:%02d:02%d", $hourz,$minz,$secz)
  }
  # print 3 seconds as 00:03, so timestamp always has a colon.
  return sprintf ("%02d:%02d", $minz,$secz)
}

sub hms_to_seconds {
    # hms_to_seconds(1:2:3.4) => 1h.2m.3.4s = 1*3600+2*60+3 = 3723.
    my $hms = $_[0];
    if ( $hms =~ m,^(\d+):(\d+):(\d+), ) { # ignore trailing chars.
      return $1 * 3600 + $2 * 60 + $3;
    }elsif( $hms =~ m,^(\d+):(\d+), ) {
      return $1 * 60 + $2;
    }
    return $hms;
}

CommentsEdit

That's a lot of script for a tip. I have done some quick formatting, but have not thought about the proposed tip yet. One point is that the "Mosh" function names are a bit jarring and should be replaced with s:. JohnBeckett (talk) 05:16, August 24, 2013 (UTC)

Around Wikia's network

Random Wiki