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® 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