Open Source im professionellen Einsatz
Linux-Magazin 05/2010
© Martin Wendring, Pixelio.de

© Martin Wendring, Pixelio.de

Perl-Skript steuert Videowiedergabe

Episodenfilm

Ein Perl-Skript mit GTK-2-Oberfläche merkt sich, wie weit sein Anwender gespeicherte Videos angesehen hat, und fährt auf Wunsch an der Stelle der letzten Unterbrechung wieder fort.

1147

Wer wie ich im Bus auf dem Weg zur Arbeit mit dem Netbook Fernsehfilme ansieht, der muss mit an Sicherheit grenzender Wahrscheinlichkeit genau an der spannendsten Stelle umsteigen. Bleiben auf dem nächsten Teilstück nur 15 Minuten, dann lohnt es sich vielleicht nicht, nur eine weitere Szene des Films zu verfolgen. Stattdessen sehe ich mir dann auf der Kurzstrecke lieber die ebenfalls aufgezeichnete Tagesschau [2] an. Um den angefangenen Film aber später doch fertig zu sehen, genügt ein Klick (siehe Abbildung 1) und das Programm fährt genau dort fort, wo der User den Lauf vorher gestoppt hatte.

Das Durcheinander verhindern

Eine Zeitmaschine für gespeicherte Videos also, genau so, wie das mein Tivo [3] seit mehr als zehn Jahren zu Hause in San Francisco macht. Der digitale Videorekorder speichert eine Reihe von Fernsehsendungen und dazu jeweils den Zeitstempel der letzten Unterbrechung. Wählt man später wieder einen dieser Filme aus der Liste, nimmt der Recorder den Abspielvorgang genau dort wieder auf. I-Tunes oder Podcast-Software machen es ähnlich. Wie schwierig wäre es wohl, ein kurzes Skript mit derselben Funktion eigenhändig in Perl zu schreiben? Listing 1 zeigt mit weniger als 150 Zeilen das Ergebnis.

Abbildung 1: Die GTK-2-Oberfläche startet auf Mausdruck ausgewählte Videos an der Stelle der letzten Unterbrechung - beziehungsweise beim ersten Mal am Anfang.

Listing 1:

#!/usr/local/bin/perl -w
use strict;
use Gtk2 '-init';
use Gtk2::SimpleList;
use POE;
use POE::Wheel::Run;
use POE::Filter::Stream;
use YAML qw(LoadFile DumpFile);
 
my  ($home)    = glob "~";
my  $YAML_FILE = "$home/.ttv.dat";
my  $OFFSETS   = {};
my  $REWIND    = 10;

my @VIDEOS  = sort { -M $a <=> -M $b } 
              (<*.mp4>, <*.avi>);

if(-f $YAML_FILE) {
  $OFFSETS = LoadFile( $YAML_FILE );
}

POE::Session->create(
  inline_states => {
    _start     => \&ui_start,
    play_video => \&play_video,
    click      => \&click,
    output     => \&stdout_handler,
    play_ended => \&play_ended,
});

$poe_kernel->run();
exit 0;

###########################################
sub play_ended {
###########################################
  my($kernel, $heap) = @_[KERNEL, HEAP];

  DumpFile( $YAML_FILE, $OFFSETS );
  listbox_redraw($heap->{slist});
}

###########################################
sub click {
###########################################
  my($kernel, $session, $gtk_list_data) =
                 @_[KERNEL, SESSION, ARG1];

  my ($sl, $path) = @$gtk_list_data;
  my $row_ref = 
      $sl->get_row_data_from_path($path);

    $kernel->yield("play_video", 
                   $row_ref->[1]);
}

###########################################
sub ui_start {
###########################################
  my ($kernel, $session, $heap) = 
                 @_[KERNEL, SESSION, HEAP];

  $heap->{main_window} = 
            Gtk2::Window->new ('toplevel');

  $kernel->signal_ui_destroy(
                $heap->{main_window});

  $heap->{slist} = Gtk2::SimpleList->new (
    'Timer'    => 'text',
    'Video'    => 'text',
  );

  listbox_redraw( $heap->{slist} );

  $heap->{slist}->signal_connect(
      row_activated => 
          $session->callback("click"));

  $heap->{main_window}->add(
                         $heap->{slist});
  $heap->{main_window}->show_all;
}

###########################################
sub listbox_redraw {
###########################################
    my($slist) = @_;

    @{$slist->{data}} = (
       map { [ timer($_), $_ ] } @VIDEOS
    );
}

###########################################
sub timer {
###########################################
    my($video) = @_;

    my $sec = 0;
    $sec = $OFFSETS->{$video} if 
                exists  $OFFSETS->{$video};

    return sprintf("%02d:%02d:%02d", 
        int($sec/(60*60)), 
        ($sec/60)%60, $sec%60);
}

###########################################
sub play_video {
###########################################
  my ($kernel, $session, $heap, $video) =
           @_[KERNEL, SESSION, HEAP, ARG0];

  my $offset = 0;

  $offset = $OFFSETS->{ $video } - $REWIND 
    if exists $OFFSETS->{ $video } 
       and $OFFSETS->{ $video } > $REWIND;

  my $wheel =
    POE::Wheel::Run->new(
      Program     => "/usr/bin/mplayer",
      ProgramArgs => 
          ["-fs", "-ss", $offset, $video],
      StdoutFilter => 
               POE::Filter::Stream->new(),
      StdoutEvent => 'output',
      CloseEvent  => 'play_ended',
  );

  $heap->{video} = $video;
  $kernel->sig_child( $wheel->PID(), 
                      'sig_child' );

  $heap->{player} = $wheel;
}

###########################################
sub stdout_handler {
###########################################
    my ($heap, $input) = @_[HEAP, ARG0];

    if($input =~ /(?:^| )V:s*([d.]+)/m) {
        $OFFSETS->{$heap->{video}} = $1;
    }
}

Nun wäre es vermessen, ein Wunderwerk der Videotechnik wie Mplayer nachzubauen. Doch bringt der Tausendsassa ja glücklicherweise bereits alle Voraussetzungen für eine Zeitmaschinensteuerung mit: Wie Abbildung 2 zeigt, zählt der laufende Mplayer stetig die verstrichenen Videosekunden hoch, es ist also für ein Steuerprogramm relativ einfach festzustellen, wie weit der Player in einem Film fortgeschritten ist. Dabei darf der User sogar mit den [Page up]- und [Page down]-Tasten während der Vorführung wild im Video hin und her springen. Eine zweite Voraussetzung für die Fernsteuerung durch ein externes Programm wie das vorgestellte Perl-Skript ist die Option »-ss n«, die den Player mit einer angefügten Sekundenzahl anweist, das Video nicht von Anfang an abzuspielen, sondern die ersten n Sekunden zu überspringen. Damit ist auch schon klar, wie »ttv« funktioniert: Die GTK-2-getriebene Benutzeroberfläche wartet darauf, dass der User ein Video doppelklickt. Ist es das erste Mal, wirft das GUI den Mplayer an und lässt ihn das Video von Anfang an abspielen. Während der Player arbeitet und der User den laufenden Film genießt, greift das Skript über Mplayers Standardausgabe die verstreichenden Videosekunden ab und speichert diese laufend zwischen.

Abbildung 2: Mplayer zählt während des Laufs die Sekunden des Videos auf der Standardausgabe hoch. Das »ttv«-Skript greift die Daten von dort ab.

Bricht der User den Abspielvorgang ab (zum Beispiel indem er im Mplayer-Fenster die Taste [Q] drückt), taucht die grafische Oberfläche wieder auf und das Skript legt die Abspieldauer unter dem Namen des Videos in der YAML-Datei »~/.ttv.dat« im Homeverzeichnis ab (Abbildung 3).

Abbildung 3: In der YAML-Datei »~/ttv.dat« legt das Skript die Spieldauer angespielter Videos fest. Hier nicht gespeicherte Videos spielt es von Anfang an ab, falls der User sie auswählt.

Tanz auf fremden Hochzeiten

Erfahrene Snapshot-Leser ahnen schon, dass ich die Steuerung der GUI-Komponenten im ruckelfreien Zusammenspiel mit einem extern aufgerufenen Programm wie Mplayer wieder einmal mit dem Event-basierten Perl-Framework POE vom CPAN realisiere. Wie auch bei Terminalsteuerungen auf Curses-Basis hopst POE mit allen erdenklichen GUI-Eventschleifen im Takt und eignet sich hervorragend dazu, quasi-parallele Prozesse zu steuern. In diesem Fall startet das GUI-Skript den Videospieler hinter den Kulissen tatsächlich als separaten Prozess, aber im Hintergrund greift POE im robusten und eleganten Single-Process-/Single-Thread-Verfahren dessen Ausgabedaten ab, springt Callback-Handler an und kon- trolliert auch, ob der Player überhaupt noch läuft oder ob der User ihn nicht etwa schon abgeschaltet hat.

Da Zeile 3 von »ttv« das GTK-2-Modul vor den POE-Modulen anfordert, ist POE darüber informiert, dass nicht seine eigene Eventschleife den Prozess steuern wird, sondern GTK 2 mit dem CPAN-Modul POE::Loop::Glib als unsichtbarer Brücke. Zeile 11 legt den Pfad der YAML-Datei fest, in der das Skript später den Hash-Inhalt der Referenz »$OFFSETS« speichert. Die Datenstruktur weist Videodateinamen jeweils einer Fließkommazahl zu, die die bereits abgespielten Sekunden angibt.

Die globale Variable »$REWIND« gibt an, dass das Skript jeweils 10 Sekunden zurückspult, bevor es wieder mitten in ein früher unterbrochenes Videos hineinspringt. Das gibt dem Zuschauer die Ge- legenheit, im Ablauf des Filmgeschehens schnell wieder Fuß zu fassen. Verfügbare Videos sucht Zeile 15 im aktuellen Verzeichnis als MP4- und AVI-Dateien zusammen. Sie ist unter Umständen anzupassen, falls der User andere Formate bevorzugt.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 4 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Ich glotz' TV

    Nina Hagens Medien-Nutzungsverhalten zu kopieren ist für einen Auslandsdeutschen gar nicht einfach. Doch ein Internet-Fernsehportal, die abgekupferte Technik eines Geräts und Perl verhelfen Exilanten sogar zu zeitversetztem Pantoffelkino. Hier Gebliebene haben natürlich genauso viel Spaß.

  • Am Fließband

    Beim halbautomatischen Umwandeln von gedruckten Zeitschriftenartikeln ins PDF-Format hilft das heute vorgestellte Perl-Skript. Den Ablauf triggert ein Taster am Scanner.

  • Netz-Journal

    Was der Rechner im eigenen Netz treibt, offenbart ein Aufruf von »netstat«. Mit ein paar Perl-Modulen lässt sich daraus ein Tool entwickeln, das die Daten dynamisch anzeigt, ganz nach dem Vorbild von Top.

  • Final Cut

    Ein handgedrehtes Video sieht mit einem Vorspann gleich professioneller aus. Die Tools Mencoder und Sox helfen bei der Formatfitzelei und ein Perl-Skript automatisiert den Vorgang.

  • Täglich auf Zack

    Wer hat schon Zeit, täglich seine Logdateien zu kontrollieren? Das im Folgenden vorgestellte Skript fasst alle aktuellen Veränderungen in einem Report zusammen und verschickt sie einmal täglich per E-Mail.

comments powered by Disqus

Ausgabe 01/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.