Vollautomatische Ausspielung von Wiederholungen und Vorproduzierten Sendungen

Nun muss ich auch mal wieder eine Frage stellen.

Wir haben einige Syndications und Wiederholungen, die ich weitestgehend automatisieren möchte.
Die Syndications werden inzwischen immer mit dem gleichen Dateinamen und einem Cue-Sheet angeliefert.
Ähnlich wie hier beschrieben: https://forum.mairlist.com/index.php/topic,9849.msg63781.html#msg63781
Ich möchte also folgendes erreichen. Zum einen möchte ich irgendwie triggern das Cue-In, Fade-Out und Cue-Out neu ermittelt werden, die sind im DB Eintrag gelöscht. (vielleicht optimalerweise bevor die Playliste für die nächste Stunde geladen wird, die das betrifft also noch in der DB) Ausserdem spätestens beim laden der Playliste das neu-einlesen des Cue-Sheets (gleicher Dateiname wie die MP3 Datei)

Trigger entweder über den mAirlist Event Scheduler oder gerne auch über das REST Interface, dann kann ich das gleich auslösen, wenn ich per Batch-Script die neuen Dateien ziehe. MP3 Gain drüber und mAirlist-Cue triggern.
mAirlist DB Server steht auch zur Verfügung und versteht auch etwas REST, wenn ich das richtig verstanden habe.

Kann man doch im Script machen mit AutoSearchPositions?

Danke, könntest Du das für den Hardwaremokel etwas detaillierter beschreiben?
Ich finde im Wiki nix zu dem Suchbegriff und hier im Forum auch nicht. Auch die Scriptinghelp liefert kein suchergebnis.
Dort habe ich nur

procedure PerformAutoCue ( iItem : IFilePlaylistItem ) 

gefunden.

Und was genau wird dann ausgelöst, Auto-Cue und neueinlesen eines Cue Sheets oder nur eins von beiden?

Für einen einzelnen Cuepunkt (Cue In, Fade Out oder Cue Out) reicht:

item.AutoSearchPosition(ptFadeOut); // oder ptCueIn oder ptCueOut

Cue-Sheet einzeln einlesen geht so:

item.GetCueData.LoadFromCueSheet('C:\irgendwo.cue');

OK, ich stehe immer noch total auf dem Schlauch.
Bisher habe ich an mAirlist Basisfunktionen wie Encoder herumgescriptet. Ich muss erst mal verstehen, wie ich das Script dazu bewege, bestimmte Elemente einer Playliste zu bearbeiten.
Geht das nur in der Hauptplayliste oder kann ich das schon in der Datenbank triggern, bevor die ausspielende Instanz die Playliste überhaupt geladen hat?

Ich habe hier von KFB ein Script gefunden, das müsste als Ausgangsbasis ja funktionieren, nur eben nicht Hooks bearbeiten sondern CuePunkte und Cuesheet:

const
      MAX_COUNT = 2;

    var
      pl: IPlaylist;
      hook: IPlaylistItem;
      i: integer;

    begin
      pl := Factory.CreatePlaylist;

      CurrentPlaylist.BeginUpdate;
      try
        for i := CurrentPlaylist.GetNextIndex to CurrentPlaylist.GetCount - 1 do
          if CurrentPlaylist.GetItem(i).GetItemType = pitMusic then begin
            pl.Add(CurrentPlaylist.GetItem(i));
            if pl.GetCount = MAX_COUNT then break;
          end;

        hook := Factory.CreateHookContainer(pl);
        container.GetPlaylist.Add(hook);
      finally
        CurrentPlaylist.EndUpdate;
      end;
    end.[/code]

    Ich habe das also mal so umgebaut, evetl. muss ich den Elementtyp noch mal ändern, spielt aber für die Funktion, erstmal keine Rolle.

    const
      MAX_COUNT = 3;

    var
      pl: IPlaylist;
      cue: IPlaylistItem;
      i: integer;

    begin
      pl := Factory.CreatePlaylist;

      CurrentPlaylist.BeginUpdate;
      try
        for i := CurrentPlaylist.GetNextIndex to CurrentPlaylist.GetCount - 1 do
          if CurrentPlaylist.GetItem(i).GetItemType = pitMusic then begin
            pl.Add(CurrentPlaylist.GetItem(i));
            if pl.GetCount = MAX_COUNT then break;
          end;

        cue := item.AutoSearchPosition(ptCueIn);
        cue := item.AutoSearchPosition(ptFadeOut);
        cue := item.AutoSearchPosition(ptCueOut);
      finally
        CurrentPlaylist.EndUpdate;
      end;
    end.

EDIT: gehört also eigentlich eher in die Foren Kategorie Scripting, ggf. bitte verschieben.

CurrentPlaylist steht, wieder Name schon vermuten lässt, für die “aktuelle Playlist”. Je nach Kontext, in dem das Script läuft. Als Event oder manuell gestartet ist das die aktuelle Playlist im Ausspieler. Als “Nachbearbeitungs-Script” in einer Datenbank-Aktion hingegen bezieht sich CurrentPlaylist auf die aus der DB geladene Playlist, bevor sie dann in die Ausspielung kopiert wird.

Natürlich kann man auch Scripts bauen, die sich einen Sendeplan aus der DB laden, ihn bearbeiten und dann zurückschreiben.

Wenn man ein bestimmtes Element sucht, geht man normalerweise einfach die CurrentPlaylist von 0 bis GetCount-1 durch und vergleicht Interpret oder Titel oder irgendein Attribut. Du musst halt wissen, woran du deine Datei erkennst :wink:

Übrigens liefert AutoSearchPosition keinen Wert zurück, sondern trägt ihn direkt in das IPlaylistItem ein.

OK, sieht so aus als wäre das Nachbearbeitungsscript, das was ich brauche. Mal sehen ob ich etwas dazu finde, wie das getriggert wird. Ich versinke also mal etwas in der Doku. Bitte nicht spoilern, nur wer selber sucht, lernt auch was.

Ja, das ist klar soweit. Ich bin mir noch nicht sicher ob ich die Sendung in einem Stück verwende oder die Version, die in 3 Teilen angeliefert wird aber das spielt ja keine Rolle. Ich bin hier von 3 Elementen pro Sendestunde ausgegangen, die ich dann entsprechend Markiere, vermutlich werde ich nach dem Elemettyp Sendung, suchen, der dann 3 mal auftaucht und bearbeitet werden soll.

OK, ich brauche die Variable cue also gar nicht und wenn ich einfach alle durchlaufe kann ich break und MAXCOUNT auch rauswerfen.

const
  MAX_COUNT = 3;

var
  pl: IPlaylist;
  i: integer;

begin
  pl := Factory.CreatePlaylist;

  CurrentPlaylist.BeginUpdate;
  try
    for i := CurrentPlaylist.GetNextIndex to CurrentPlaylist.GetCount - 1 do
      if CurrentPlaylist.GetItem(i).GetItemType = pitShow then begin
        pl.Add(CurrentPlaylist.GetItem(i));
        if pl.GetCount = MAX_COUNT then break;
      end;

     item.AutoSearchPosition(ptCueIn);
     item.AutoSearchPosition(ptFadeOut);
     item.AutoSearchPosition(ptCueOut);
  finally
    CurrentPlaylist.EndUpdate;
  end;
end.

Das einlesen des Cuesheets fehlt auch noch dazu müsste ich aus dem Item das ich gerade bearbeite den Pfad samt Dateinamen extrahieren und ein .cue anhängen um es der entsprechenden Funktion zu übergeben, das Sheet neu zu lesen.

OK, das war schon mal einfach, ohne in die Doku zu schauen.
Selbsterklärende Dialogboxen sind schon was feines.


Nachbearbeitungsscript-Playliste.PNG

Auf meinem Weg zur Vollautomatischen Ausspielung unserer Wiederholgunen, bin ich nun mit den externen Elementen ein Stück weiter. (Finaler Test steht noch aus und kann wegen des laufenden Programms, gerade nicht durchgeführt werden)

Jetzt geht es hier weiter, mit dem Eincuen, das sollte mit dem Script oben, funktionieren und was mir auch noch fehlt:

  • neu einlesen des ID3 Tags
  • ggf. vorhandenes Cue-Sheet lesen und anwenden
  • Einen Platzhalter mit einem Befehl austauschen

OK, ich habe hier einen Fehler, wenn das Script ausgeführt werden soll.

const
  MAX_COUNT = 3;

var
  pl: IPlaylist;
  i: integer;

begin
  pl := Factory.CreatePlaylist;

  CurrentPlaylist.BeginUpdate;
  try
    for i := CurrentPlaylist.GetNextIndex to CurrentPlaylist.GetCount - 1 do
      if CurrentPlaylist.GetItem(i).GetItemType = pitShow then begin
        pl.Add(CurrentPlaylist.GetItem(i));
        if pl.GetCount = MAX_COUNT then break;
      end;

     item.AutoSearchPosition(ptCueIn);
     item.AutoSearchPosition(ptFadeOut);
     item.AutoSearchPosition(ptCueOut);
  finally
    CurrentPlaylist.EndUpdate;
  end;
end.

Da erscheint dieser Fehler, scheint also irgend etwas nicht zu stimmen.

Einen CurrentPlaylist.GetNextIndex gibt es nicht, da die Liste ja noch nicht in die Ausspielung geladen ist. Setz also einfach eine 0 ein.

Malte vs. Script
Script: 2
Malte: 0

Neuer Fehler, ob ich das jemals lerne… Dabei habe ich damals in der Schule sogar etwas Pascal gelernt. Auf Hochmodernen 286ern mit Bernsteinfarbenen Monitoren. :disappointed_relieved:

const
  MAX_COUNT = 3;

var
  pl: IPlaylist;
  i: integer;

begin
  pl := Factory.CreatePlaylist;

  CurrentPlaylist.BeginUpdate;
  try
    for i := 0 to CurrentPlaylist.GetCount - 1 do
      if CurrentPlaylist.GetItem(i).GetItemType = pitShow then begin
        pl.Add(CurrentPlaylist.GetItem(i));
        if pl.GetCount = MAX_COUNT then break;
      end;

     item.AutoSearchPosition(ptCueIn);
     item.AutoSearchPosition(ptFadeOut);
     item.AutoSearchPosition(ptCueOut);
  finally
    CurrentPlaylist.EndUpdate;
  end;
end.

Ich muss dieses Thema noch mal hoch bringen, kann mir da jemand weiter helfen, was an meinem Script falsch ist?

Das „end.“ fehlt?

Ergänzte Grüße

TSD

Vor allem fehlt die Deklaration von “item”. Für welche Items soll das AutoCue denn durchgeführt werden? Für alle, die vorher in der Liste “pl” notiert wurden? Dann wohl eher so:

for i := 0 to pl.GetCount - 1 do begin
   pl.GetItem(i).AutoSearchPosition(ptCueIn);
   pl.GetItem(i).AutoSearchPosition(ptFadeOut);
   pl.GetItem(i).AutoSearchPosition(ptCueOut);
end;

Danke, das war tatsächlich nur ein Copy and Paste Fehler hier im Forum. Im Script hatte ich das end. mit drin. Ich korrigiere das weiter oben in meinen Posts.

d.H. ich brauche eine 2. Schleife. Die erste, die mir den Typ Sendung (Show) raus sucht aber nur maximal die ersten 3 Elemente durchsucht und die 2. Schleife die die Elemente bearbeitet.

Laufen die einfach nacheinander? Ich hätte jetzt angenommen, die müssten in einander verschachtelt sein?

Dann komme ich auf dieses Konstrukt.

const
  MAX_COUNT = 3;

var
  pl: IPlaylist;
  i: integer;

begin
  pl := Factory.CreatePlaylist;

  CurrentPlaylist.BeginUpdate;
  try
    for i := 0 to CurrentPlaylist.GetCount - 1 do
      if CurrentPlaylist.GetItem(i).GetItemType = pitShow then begin
        pl.Add(CurrentPlaylist.GetItem(i));
        if pl.GetCount = MAX_COUNT then break;
      end;

	for i := 0 to pl.GetCount - 1 do begin
		pl.GetItem(i).AutoSearchPosition(ptCueIn);
		pl.GetItem(i).AutoSearchPosition(ptFadeOut);
		pl.GetItem(i).AutoSearchPosition(ptCueOut);
	end;
  finally
    CurrentPlaylist.EndUpdate;
  end;
end.

EDIT nach dem Stundenwechsel: Sehr schön, der Fehler ist schon mal weg.

Der nächste Schritt, den ich machen möchte ist: Den Titel und Interpreten aus dem MP3 Tag neu lesen.

Ich nehme an, dass das irgendwie mit: IMetadataHandler > ReadNativeTags funktioniert.

Die untere for Schleife um sowas erweitern oder ist das fehlt mir da wieder irgend etwas?

pl.GetItem(i).IMetadataHandler(ReadNativeTags);

Das würde ich an oberster Stelle einfügen, bevor die Cue-Werte ermittelt werden.

EDIT oder ist es:
IFilePlaylistItem > function GetFileTagData ( iTag : string ) : string
?

Hatte diesen Beitrag gefunden: Zugriff auf ID3-Tags mit mAirList-Script
Weiß aber nicht ob der a) noch aktuell ist und b) kann ich daraus auch nicht das richtige Script ableiten.

Wenn du sie nacheinander schreibst, laufen sie auch nacheinander :wink: Natürlich könnte man auch auf die zweite Schleife verzichten und alles direkt in der ersten machen (und dabei mitzählen, wie viele man schon bearbeitet hat).

const
  MAX_COUNT = 3;

var
  i: integer;
  count: integer;

begin
  CurrentPlaylist.BeginUpdate;
  try
    count := 0;
    for i := 0 to CurrentPlaylist.GetCount - 1 do
      if CurrentPlaylist.GetItem(i).GetItemType = pitShow then begin
        CurrentPlaylist.GetItem(i).AutoSearchPosition(ptCueIn);
        CurrentPlaylist.GetItem(i).AutoSearchPosition(ptFadeOut);
        CurrentPlaylist.GetItem(i).AutoSearchPosition(ptCueOut);

        count := count + 1;
        if count = MAX_COUNT then break;
      end;
  finally
    CurrentPlaylist.EndUpdate;
  end;
end.

Probier mal das:

Factory.CreateMetadataHandler(CurrentPlaylist.GetItem(i)).ReadNativeTags;

Danke!

Das ist genau so’n Ding was ich dann immer nicht verstehe. (Das war Westfälisch: Immer nicht verstehe :joy:)
Die Cue Daten kann ich einfach vom Item Lesen aber um das lesen der Tags, muss ich wieder eine weitere Funktion drum herum bauen.
Ob ich das jemals in meinen Kopf kriege…:exploding_head:

Also, dann mal ganz langsam:

  • Factory ist ein Objekt, das Methoden zur Verfügung stellt, über die man alle möglichen anderen Arten von Objekten erzeugen kann.
  • In diesem Fall wollen wir über die Funktion CreateMetadataHandler ein Objekt vom Typ IMetadataHandler erzeugen, mit dem wir die Tags auslesen können.
  • Die Funktion CreateMetadataHandler bekommt dabei als Parameter das PlaylistItem übergeben, auf dem der Handler arbeiten soll - damit der direkt weiß, mit welcher Datei und welchem Item er es zu tun hat.
  • Dann wird dessen Methode ReadNativeTags aufgerufen, um die ID3-Tags zu lesen und in den PlaylistItem-Datensatz zu kopieren.

“Unabgekürzt” (und besser zu verstehen) würde das so aussehen:

item := CurrentPlaylist.GetItem(i);
handler := Factory.CreateMetadataHandler(item);
handler.ReadNativeTags;

Durch die Kurzschreibweise ersparen wir uns die ganzen Variablendeklarationen.

Du siehst, die Interna von mAirList sind manchmal sehr kompliziert. Aber bieten doch eine Menge Funktionalität. Die Script-Engine ist euer Guckloch in diese Welt.

Um die Sache etwas intuitiver zu machen, könnte ich dem IPlaylistItem eine Methode CreateMetadataHandler geben, die nur eine Abkürzung für Factory.CreateMetadataHandler(item) ist. Dann würde es so aussehen:

CurrentPlaylist.GetItem(i).CreateMetadataHandler.ReadNativeTags;

Wäre das besser zu verstehen?

Übrigens, bei den Typen mit “I” vorne (IPlaylistItem, IMetadataHandler, …) handelt es sich allesamt um “Interfaces”, also “indirekte” Verweise auf Objekte. Die dann wiederum Methoden haben, die man aufrufen kann.

Die Verwendung von Interfaces statt normaler Objektreferenzen erlaubt es in Delphi, dass man “reference counting” macht. Also Objekte, die sich selbst aus dem Speicher löschen, sobald niemand mehr auf sie zugreift. Daher der Umweg über Interfaces. Beim Programmieren immer die doppelte Arbeit, da man sowohl das Interface als auch das implementierende Objekt definieren muss. Aber am Ende hat es nur Vorteile.

1 Like