Le lundi 22 mars 2010 08:35:19, jonas ohrstrom a écrit :
> yes it looks a bit hackysh :) but it shows that/how this can be achieved in
>  liquidsoap! cleaning up the script before putting it into production then
>  should be the smaller issue :)

I think I can propose a first shot at something clean :-)

After discussin with David, i have extended the possible types for protocols 
into two types: resolver - which create fresh files, and post-processor, which 
operate on a file, possibly creating a new fresh file.

Attached is a patch that implements this. It is working fine. However, I am 
wondering if we should apply post-processor to all the local files or only the 
last one.

Hence, this patch needs a little review, but we are close to a proper solution
now :-)

You can test the patch with, for instance:
  "cut_file:cut_start=5:annotate:bli=\"bla\":replay_gain:/tmp/bla.mp3"

And get a trace like this:
request.trace 0
[2010/03/22 21:46:18] Pushed 
["cut_file:cut_start=5:annotate:bli=\"bla\":replay_gain:/tmp/bla.mp3";...].
[2010/03/22 21:46:18] Adding post-process cut_file with parameters 
cut_start=5...
[2010/03/22 21:46:18] Pushed 
["annotate:bli=\"bla\":replay_gain:/tmp/bla.mp3";...].
[2010/03/22 21:46:18] Adding post-process annotate with parameters bli="bla"...
[2010/03/22 21:46:18] Pushed ["replay_gain:/tmp/bla.mp3";...].
[2010/03/22 21:46:18] Adding post-process replay_gain with no parameters...
[2010/03/22 21:46:18] Pushed ["/tmp/bla.mp3";...].
[2010/03/22 21:46:18] Post-processing /tmp/bla.mp3 with post_processor 
replay_gain...
[2010/03/22 21:46:19] Pushed ["/tmp/bla.mp3";...].
[2010/03/22 21:46:19] Post-processing bli="bla":/tmp/bla.mp3 with 
post_processor annotate...
[2010/03/22 21:46:19] Pushed ["/tmp/bla.mp3";...].
[2010/03/22 21:46:19] Post-processing cut_start=5:/tmp/bla.mp3 with 
post_processor cut_file...
[2010/03/22 21:46:19] Pushed ["/tmp/liqI9cS.osb";...].
[2010/03/22 21:46:19] Currently on air.



Romain
Index: scripts/utils.liq
===================================================================
--- scripts/utils.liq	(révision 7225)
+++ scripts/utils.liq	(copie de travail)
@@ -487,13 +487,50 @@
  extract_replaygain = "#{configure.libdir}/extract-replaygain"
  x = get_process_lines("#{extract_replaygain} #{quote(arg)}")
  if list.hd(x) != "" then
-  ["annotate:replay_gain=\"#{list.hd(x)}\":#{arg}"]
+  ([arg],[("replay_gain",list.hd(x))])
  else
-  [arg]
+  ([arg],[])
  end
 end
-add_protocol("replay_gain", replaygain_protocol)
+add_protocol("replay_gain", protocol_type = "post-processor", 
+             replaygain_protocol)
 
+# Register the cut protocol
+def cut_protocol(arg,delay)
+ # The extraction program
+ cut_file = "#{configure.libdir}/cut-file"
+ # Parse args 
+ ret = string.extract(pattern="cut_start=(\d+)",arg)
+ start = 
+   if list.length(ret) == 0 then 
+     "0"
+   else
+     ret["1"]
+   end
+ ret = string.extract(pattern="cut_stop=(\d+)",arg)
+ stop =
+   if list.length(ret) == 0 then
+     "0"
+   else
+     ret["1"]
+   end
+ ret = string.extract(pattern=":([^:]*)$",arg)
+ uri = 
+   if list.length(ret) == 0 then
+     ""
+   else
+     ret["1"]
+   end
+ x = get_process_lines("#{cut_file} #{quote(uri)} #{start} #{stop}")
+ if list.hd(x) != "" then
+   ([list.hd(x)],[])
+ else
+   ([uri],[])
+ end
+end
+add_protocol("cut_file", protocol_type = "post-processor", 
+             temporary=true, cut_protocol)
+
 # Enable replay gain metadata resolver. This resolver will
 # process any file decoded by liquidsoap and add a @replay_gain@
 # metadata when this value could be computed. For a finer-grained
Index: scripts/cut-file
===================================================================
--- scripts/cut-file	(révision 0)
+++ scripts/cut-file	(révision 0)
@@ -0,0 +1,87 @@
+#!/usr/bin/perl -w
+
+use strict ;
+
+my $file = $ARGV[0] || die ;
+
+my $start = $ARGV[1] || "0" ;
+
+my $stop = $ARGV[2] || "0" ;
+
+sub mktemp {
+  my $file = `mktemp --tmpdir --suffix=".osb" "liqXXXX"`;
+  $file =~ s/\n//;
+  $file;
+}
+
+my $out_file = mktemp(); 
+
+sub test_mime {
+  my $file = shift ;
+  if (`which file`) {
+    return `file -b --mime-type "$file"`;
+  }
+}
+
+if (($file =~ /\.mp3$/i) || (test_mime($file) =~ /audio\/mpeg/))  {
+
+  if (`which cutmp3`) {
+
+    my $start_option = "00:00";
+    my $stop_option = "99999:0";
+
+    if ($start ne "0") {
+       $start_option = "00:$start"
+    } 
+  
+    if ($stop ne "0") {
+       $stop_option = "00:$stop"
+    }
+ 
+    system("nice -n 20 cutmp3 -c -q -a $start_option -b $stop_option -O \"$out_file\" -i \"$file\" >/dev/null 2>\&1");
+
+  } else {
+
+    print STDERR "Cannot find cutmp3 binary!\n";
+    system("cp \"$file\" \"$out_file\"");
+
+  }
+
+} elsif (($file =~ /\.ogg$/i) || (test_mime($file) =~ /application\/ogg/)) {
+
+  if (`which vcut`) {
+
+    my $start_file = $file;
+
+    # Cut beginning
+    if ($start ne "0") {
+       $start_file = mktemp();
+       system("nice -n 20 vcut \"$file\" /dev/null \"$start_file\" +$start  >/dev/null 2>\&1"); 
+    }
+
+    # Cut end
+    if ($stop ne "0") {
+       system("nice -n 20 vcut \"$start_file\" \"$out_file\" /dev/null +$stop >/dev/null 2>\&1"); 
+       system("rm -f \"$start_file\"");
+    } else {
+      if ("$start_file" ne "$file") {
+        system("mv $start_file $out_file");
+      }
+    }
+
+  } else {
+
+    print STDERR "Cannot find vcut binary!\n";
+    system("cp \"$file\" \"$out_file\"");
+
+  }
+
+} else {
+
+  print STDERR "File format not supported...\n";
+  system("cp \"$file\" \"$out_file\"");
+
+}
+
+print "$out_file\n";
+

Modification de propriétés sur scripts/cut-file
___________________________________________________________________
Ajouté : svn:executable
   + *

Index: scripts/Makefile
===================================================================
--- scripts/Makefile	(révision 7221)
+++ scripts/Makefile	(copie de travail)
@@ -1,6 +1,6 @@
 
 DISTFILES = $(wildcard *.in) Makefile ask-liquidsoap.rb ask-liquidsoap.pl \
-	    $(wildcard *.liq) extract-replaygain
+	    $(wildcard *.liq) extract-replaygain cut-file
 
 top_srcdir = ..
 include $(top_srcdir)/Makefile.rules
Index: src/protocols/annotate.ml
===================================================================
--- src/protocols/annotate.ml	(révision 7226)
+++ src/protocols/annotate.ml	(copie de travail)
@@ -55,7 +55,8 @@
         | _ -> raise Error
     in
     let metadata,uri = parse [] in
-      [Request.indicator ~metadata:(Utils.hashtbl_of_list metadata) uri]
+      [Request.indicator ~temporary:false 
+                         ~metadata:(Utils.hashtbl_of_list metadata) uri]
   with
     | Error
     | Stream.Failure | Stream.Error _ -> log "annotate: syntax error" ; []
@@ -64,4 +65,6 @@
   Request.protocols#register "annotate"
     ~sdoc:("[annotate:key=\"val\",key2=\"val2\",...:uri] adds "^
            "the metadata to the request and is then resolved into uri")
-    { Request.resolve = annotate ; Request.static = false }
+    { Request.resolve = annotate ; 
+      Request.static = false ;
+      Request.protocol_type = Request.Post_processor }
Index: src/protocols/say.ml
===================================================================
--- src/protocols/say.ml	(révision 7226)
+++ src/protocols/say.ml	(copie de travail)
@@ -83,7 +83,9 @@
   Request.protocols#register
     ~sdoc:"Speech synthesis, with optional voice choice using say:voice/blah."
     "say"
-    { Request.resolve = say ; Request.static = true }
+    { Request.resolve = say ; 
+      Request.static = true ;
+      Request.protocol_type = Request.Resolver }
 
 let time arg ~log timeout =
   let tm = Unix.localtime (Unix.gettimeofday ()) in
@@ -101,4 +103,6 @@
     "time"
     ~sdoc:("Speech synthesis of a message where $(time) is replaced by "^
            "the current time")
-    { Request.resolve = time ; Request.static = false }
+    { Request.resolve = time ; 
+      Request.static = false ;
+      Request.protocol_type = Request.Resolver }
Index: src/protocols/lastfm_req.ml
===================================================================
--- src/protocols/lastfm_req.ml	(révision 7226)
+++ src/protocols/lastfm_req.ml	(copie de travail)
@@ -38,4 +38,6 @@
 let () =
   Request.protocols#register "lastfm"
     ~sdoc:("Play a song on last.fm.")
-    { Request.resolve = lastfm ; Request.static = false }
+    { Request.resolve = lastfm ; 
+      Request.static = false ; 
+      Request.protocol_type = Request.Resolver }
Index: src/protocols/mpd.ml
===================================================================
--- src/protocols/mpd.ml	(révision 7226)
+++ src/protocols/mpd.ml	(copie de travail)
@@ -160,4 +160,6 @@
   Request.protocols#register "mpd"
     ~sdoc:("[mpd:tag=\"value\"] finds all files with a tag equal " ^
            "to a given value using mpd.")
-    { Request.resolve = mpd ; Request.static = false }
+    { Request.resolve = mpd ; 
+      Request.static = false ;
+      Request.protocol_type = Request.Resolver }
Index: src/protocols/extproto.ml
===================================================================
--- src/protocols/extproto.ml	(révision 7226)
+++ src/protocols/extproto.ml	(copie de travail)
@@ -89,7 +89,8 @@
                   ~sdoc:(Printf.sprintf "Fetch files using %S." prog)
                   proto
                   { Request.resolve = resolve proto prog command ;
-                    Request.static = false })
+                    Request.static = false ;
+                    Request.protocol_type = Request.Resolver })
              protos
        with
          | Not_found ->
Index: src/request.mli
===================================================================
--- src/request.mli	(révision 7226)
+++ src/request.mli	(copie de travail)
@@ -92,9 +92,20 @@
   * At each step [protocol.resolve first_uri timeout] is called,
   * and the function is expected to push the new URIs in the request. *)
 
+(** A protocol is one of:
+  * - Resolver: returns a fresh local file, e.g. file:, ftp:, http: ...
+  * - Post_processor: operates on a fresh file, possibly creating a
+  *   new file. URIs for post_processors are of the form:
+  *   protocol:(args:)next_uri where next_uri defines the file
+  *   on which the post_processor will operate. A post-processing
+  *   operator should return a temporary indicator iff it creates
+  *   a new file. In this case, it *must* always create a new file. *)
+type protocol_type = Resolver | Post_processor
+
 type protocol = {
-  resolve : string -> log:(string->unit) -> float -> indicator list ;
-  static : bool
+  resolve       : string -> log:(string->unit) -> float -> indicator list ;
+  static        : bool ;
+  protocol_type : protocol_type
 }
 
 (** A static request [r] is such that every resolving leads to the same file.
Index: src/lang/lang_builtins.ml
===================================================================
--- src/lang/lang_builtins.ml	(révision 7226)
+++ src/lang/lang_builtins.ml	(copie de travail)
@@ -220,11 +220,15 @@
   let protocol_t =
     Lang.fun_t
       [false,"",Lang.string_t ; false,"",Lang.float_t]
-      (Lang.list_t Lang.string_t)
+      (Lang.product_t (Lang.list_t Lang.string_t) Lang.metadata_t)
   in
     add_builtin "add_protocol" ~cat:Liq ~descr:"Register a new protocol."
       ["temporary",Lang.bool_t,Some (Lang.bool false),
        Some "if true, file removed when it is finished.";
+       "protocol_type",Lang.string_t, None,
+       Some "Should be one of: \"resolver\", if the \
+             protocol resolves new files, or \"post-processor\" \
+             if the protocol operates on resolved files.";
        "",Lang.string_t,None,None ;
        "",protocol_t,None,None ]
       Lang.unit_t
@@ -232,8 +236,19 @@
          let name = Lang.to_string (Lang.assoc "" 1 p) in
          let f = Lang.assoc "" 2 p in
          let temporary = Lang.to_bool (List.assoc "temporary" p) in
+         let protocol_type = 
+           let v = List.assoc "protocol_type" p in
+           match Lang.to_string v with
+             | "resolver" -> Request.Resolver
+             | "post-processor" -> Request.Post_processor
+             | _ ->
+               raise (Lang.Invalid_value
+                       (v, "Valid values are 'resolver' \
+                            or 'post-processor'"))
+         in
            Request.protocols#register name
              { Request.static = false ;
+               Request.protocol_type = protocol_type ;
                Request.resolve =
                  fun arg ~log timeout ->
                    let l =
@@ -241,10 +256,14 @@
                        f ["",Lang.string arg;
                           "",Lang.float timeout]
                    in
+                   let indicators,metadata = 
+                     Lang.to_product l 
+                   in
+                   let metadata = Lang.to_metadata metadata in
                      List.map
                        (fun s ->
-                          Request.indicator ~temporary (Lang.to_string s))
-                       (Lang.to_list l) } ;
+                          Request.indicator ~temporary ~metadata (Lang.to_string s))
+                       (Lang.to_list indicators) } ;
            Lang.unit)
 
 let () =
Index: src/request.ml
===================================================================
--- src/request.ml	(révision 7226)
+++ src/request.ml	(copie de travail)
@@ -184,8 +184,14 @@
   metadata : metadata
 }
 
-type status = Idle | Resolving | Ready | Playing | Destroyed
+type post_processor = {
+  protocol   : string ;
+  parameters : string ;
+  handler    : string -> log:(string->unit) -> float -> indicator list 
+}
 
+type status = Idle | Resolving | Post_processing | Ready | Playing | Destroyed
+
 type t = {
   id : int ;
   initial_uri : string ;
@@ -207,6 +213,7 @@
   log : log ;
   mutable root_metadata : metadata ;
   mutable indicators : indicator list list ;
+  mutable post_processors : post_processor list ;
   mutable decoder : (unit -> Decoder.file_decoder) option ;
 }
 
@@ -297,6 +304,16 @@
     t.decoder <- None ;
     if repop then pop_indicator t
 
+let push_post_processor t x = 
+  t.post_processors <- x::t.post_processors
+
+let pop_post_processor t = 
+  match t.post_processors with
+    | x::rem -> 
+        t.post_processors <- rem;
+        x
+    | [] -> raise Not_found
+
 let mresolvers_doc =
   "Methods to extract metadata from a file."
 let mresolvers =
@@ -336,13 +353,7 @@
     let hd = List.hd l in
       add_log t (Printf.sprintf "Pushed [%S;...]." hd.string) ;
       t.indicators <- l::t.indicators ;
-      t.decoder <- None ;
-      (* Performing a local check is quite fast and allows the request
-       * to be instantly available if it is only made of valid local files,
-       * without any need for a resolution process. *)
-      (* TODO sometimes it's not that fast actually, and it'd be nice
-       * to be able to disable this check in some cases, like playlist.safe. *)
-      local_check t
+      t.decoder <- None
 
 let is_ready t =
   t.indicators <> [] &&
@@ -414,6 +425,7 @@
       (match t.status with
          | Idle -> "idle"
          | Resolving -> "resolving"
+         | Post_processing -> "post-processing"
          | Ready -> "ready"
          | Playing -> "playing"
          | Destroyed -> "destroyed")
@@ -470,7 +482,8 @@
     decoder = None ;
     log = Queue.create () ;
     root_metadata = Hashtbl.create 10 ;
-    indicators = [] }
+    indicators = [] ;
+    post_processors = [] }
   in
     register t ;
     List.iter
@@ -512,9 +525,12 @@
 
 (** Plugins registration. *)
 
+type protocol_type = Resolver | Post_processor 
+
 type protocol = {
-  resolve : string -> log:(string->unit) -> float -> indicator list ;
-  static : bool ;
+  resolve        : string -> log:(string->unit) -> float -> indicator list ;
+  static         : bool ;
+  protocol_type : protocol_type
 }
 
 let protocols_doc =
@@ -548,36 +564,117 @@
     (* If the file is local we only need to check that it's valid,
      * we'll actually do that in a single local_check for all local indicators
      * on the top of the stack. *)
-    if Sys.file_exists i.string then local_check t else
+    if Sys.file_exists i.string then t.status <- Post_processing else
     let proto,arg = parse_uri i.string in
       match protocols#get proto with
         | Some handler ->
-            add_log t
-              (Printf.sprintf
-                 "Resolving %S (timeout %.fs)..."
-                 i.string timeout) ;
-            let production = handler.resolve ~log:(add_log t) arg maxtime in
-              if production = [] then begin
-                log#f 4
-                  "Failed to resolve %S! \
-                   For more info, see server command 'trace %d'."
-                  i.string t.id ;
-                ignore (pop_indicator t)
-              end else
-                push_indicators t production
+           begin
+            match handler.protocol_type with
+              | Resolver ->
+                 add_log t
+                   (Printf.sprintf
+                      "Resolving %S (timeout %.fs)..."
+                      i.string timeout) ;
+                 let production = handler.resolve ~log:(add_log t) arg 
+                                  maxtime 
+                 in
+                 if production = [] then begin
+                  log#f 4
+                    "Failed to resolve %S! \
+                     For more info, see server command 'trace %d'."
+                    i.string t.id ;
+                  ignore (pop_indicator t)
+                end else
+                  push_indicators t production
+              | Post_processor -> 
+                (* Retrieve parameters *)
+                let params,next = 
+                  let x = parse_uri arg in
+                  if x = ("","") then
+                    "",arg
+                  else
+                    x
+                in
+                let parameters = 
+                  if params = "" then "no parameters" 
+                  else Printf.sprintf "parameters %s" params
+                in
+                add_log t
+                  (Printf.sprintf
+                     "Adding post-process %s with %s..." 
+                   proto parameters) ;
+                (* Store handler and params *)
+                push_post_processor t 
+                  { protocol   = proto ;
+                    parameters = params ;
+                    handler    = handler.resolve } ;
+                (* Push the next part *)
+                push_indicators t [ { string    = next ;
+                                      temporary = false ;
+                                      metadata  = Hashtbl.create 0 }]
+           end
         | None ->
             add_log t "Unknown protocol!" ;
             pop_indicator t
   in
+  let rec post_process () = 
+    let timeleft = maxtime -. (Unix.time ()) in
+    if timeleft > 0. then
+     begin
+      let i = peek_indicator t in
+      try
+        let processor = pop_post_processor t in
+        let uri = 
+          if processor.parameters <> "" then
+            (Printf.sprintf "%s:%s") processor.parameters i.string 
+          else
+            i.string
+        in
+        add_log t
+           (Printf.sprintf
+              "Post-processing %s with post_processor %s..."
+            uri processor.protocol) ;
+        let production = processor.handler ~log:(add_log t) uri maxtime in
+          begin
+            if production = [] then begin
+              log#f 4
+                "Failed to post-process %s:%s! \
+                 For more info, see server command 'trace %d'."
+                processor.protocol uri t.id ;
+            end else
+              begin
+               (* Merge old metadata with new metadata *)
+               let merge x y = 
+                 Hashtbl.iter (Hashtbl.add x) y ;
+                 x 
+               in
+               let production = 
+                 List.map 
+                   (fun x -> { x with metadata = merge x.metadata i.metadata })
+                 production
+               in
+               push_indicators t production
+              end
+          end ;
+        post_process ()
+      with
+        | Not_found -> local_check t
+     end
+    else
+      ( add_log t "Global timeout." ; raise ExnTimeout )
+  in
   let result =
     try
-      while not (is_ready t) do
+     (* Get a local file from the resolvers. *)
+      while not (t.status = Post_processing) do
         let timeleft = maxtime -. (Unix.time ()) in
           if timeleft > 0. then
             resolve_step ()
           else
             ( add_log t "Global timeout." ; raise ExnTimeout )
       done ;
+      (* Apply post_processors *)
+      post_process () ;
       Resolved
     with
       | ExnTimeout -> Timeout
Index: Makefile
===================================================================
--- Makefile	(révision 7221)
+++ Makefile	(copie de travail)
@@ -76,6 +76,7 @@
 	$(INSTALL_DIRECTORY) $(libdir)/liquidsoap/$(libs_dir_version)
 	$(INSTALL_PROGRAM) scripts/liquidtts $(libdir)/liquidsoap/$(libs_dir_version)
 	$(INSTALL_PROGRAM) scripts/extract-replaygain $(libdir)/liquidsoap/$(libs_dir_version)
+	$(INSTALL_PROGRAM) scripts/cut-file $(libdir)/liquidsoap/$(libs_dir_version)
 	$(INSTALL_DATA) scripts/utils.liq $(libdir)/liquidsoap/$(libs_dir_version)
 	$(INSTALL_DIRECTORY) ${sysconfdir}/liquidsoap
 	$(INSTALL_DATA) examples/radio.liq \
------------------------------------------------------------------------------
Download Intel&#174; Parallel Studio Eval
Try the new software tools for yourself. Speed compiling, find bugs
proactively, and fine-tune applications for parallel performance.
See why Intel Parallel Studio got high marks during beta.
http://p.sf.net/sfu/intel-sw-dev
_______________________________________________
Savonet-devl mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/savonet-devl

Répondre à