Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement cue file parser using liq code #4330

Merged
merged 10 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ repos:
hooks:
- id: codespell
args: [-w, --ignore-words=.codespellignore]
exclude: ^doc/orig/fosdem2020
exclude: ^(doc/orig/fosdem2020|tests/language/cue_test.liq)

- repo: local
hooks:
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ Changed:

- Make alsa I/O work with buffer size different than
liquidsoap internal frame (#4236)
- Reimplemented CUE file parser in native liquidsoap script,
added support for multiple files and EAC non-compliant extension
(#1373, #4330)
- Make `"song"` metadata mapping to `"title"` metadata in
`input.harbor` disabled when either `"artist"` or `"title"`
is also passed. Add a configuration key to disable this mechanism.
Expand Down
16 changes: 16 additions & 0 deletions src/core/builtins/builtins_resolvers.ml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,22 @@ let _ =
if reentrant then reentrant_decoders := format :: !reentrant_decoders;
Lang.unit)

let _ =
Lang.add_builtin ~base:Builtins_sys.playlist_parse "get_file"
~category:`Liquidsoap ~descr:"Resolve a uri relative to a given pwd."
[
( "pwd",
Lang.nullable_t Lang.string_t,
Some Lang.null,
Some "Current directory to use for relative file path." );
("", Lang.string_t, None, Some "URI");
]
Lang.string_t
(fun p ->
let pwd = Lang.to_valued_option Lang.to_string (List.assoc "pwd" p) in
let uri = Lang.to_string (List.assoc "" p) in
Lang.string (Playlist_parser.get_file ?pwd uri))

let add_playlist_parser ~format name (parser : Playlist_parser.parser) =
let return_t = Lang.list_t (Lang.product_t Lang.metadata_t Lang.string_t) in
Lang.add_builtin ~base:Builtins_sys.playlist_parse name ~category:`Liquidsoap
Expand Down
180 changes: 0 additions & 180 deletions src/core/playlists/playlist_basic.ml
Original file line number Diff line number Diff line change
Expand Up @@ -118,187 +118,7 @@ let parse_scpls ?pwd string =
let urls = List.filter (fun s -> s <> "") urls in
List.map (fun t -> ([], Playlist_parser.get_file ?pwd t)) urls

type cue_track = {
number : int;
track_performer : string option;
track_title : string option;
indexes : (int, float) Hashtbl.t;
}

type cue_sheet = {
file : string;
performer : string option;
title : string option;
tracks : cue_track list;
}

let parse_optional parser f =
try parser (fun x -> f (Some x)) with _ -> f None

let parse_maybe_escaped escaped non_escaped f =
try escaped f with _ -> non_escaped f

let parse_file s f =
let ret =
try
parse_maybe_escaped (Scanf.sscanf s "FILE %S %s")
(Scanf.sscanf s "FILE %s %s") (fun x _ -> x)
with _ -> raise Not_found
in
f ret

let parse_title s =
parse_optional
(parse_maybe_escaped
(Scanf.sscanf s "TITLE %S")
(Scanf.sscanf s "TITLE %s"))

let parse_performer s =
parse_optional
(parse_maybe_escaped
(Scanf.sscanf s "PERFORMER %S")
(Scanf.sscanf s "PERFORMER %s"))

let parse_track s f =
try Scanf.sscanf s "TRACK %i %s" (fun i _ -> f i) with _ -> raise Not_found

let parse_index s f =
try
Scanf.sscanf s "INDEX %i %i:%i:%i" (fun index min sec frames ->
let position =
(float_of_int min *. 60.)
+. float_of_int sec
+. (float_of_int frames /. 75.)
in
f index position)
with _ -> raise Not_found

let find_file l =
let rec find cur rem =
match rem with
| x :: rem -> (
try parse_file x (fun title -> (title, cur, rem))
with Not_found -> find (x :: cur) rem)
| [] -> raise Not_found
in
let title, cur, rem = find [] l in
(List.rev cur @ rem, title)

let parse_tracks index lines =
let rec parse tracks track rem =
match rem with
| [] -> List.rev (track :: tracks)
| x :: rem -> (
try
parse_track x (fun index ->
let tracks = track :: tracks in
let track =
{
number = index;
track_title = None;
track_performer = None;
indexes = Hashtbl.create 1;
}
in
parse tracks track rem)
with _ ->
let track =
parse_title x (fun title ->
if title <> None then { track with track_title = title }
else track)
in
let track =
parse_performer x (fun performer ->
if performer <> None then
{ track with track_performer = performer }
else track)
in
begin
try parse_index x (Hashtbl.replace track.indexes)
with Not_found -> ()
end;
parse tracks track rem)
in
let track =
{
number = index;
track_title = None;
track_performer = None;
indexes = Hashtbl.create 1;
}
in
parse [] track lines

let parse_cue ?pwd string =
let strings = split_lines string in
let strings =
List.map
(fun string ->
Re.Pcre.substitute ~rex:(Re.Pcre.regexp "^\\s+")
~subst:(fun _ -> "")
string)
strings
in
let strings = List.filter (fun s -> s <> "") strings in
let strings, file = find_file strings in
let rec parse sheet rem =
match rem with
| [] -> sheet
| x :: rem -> (
let sheet =
parse_title x (fun title ->
if title <> None then { sheet with title } else sheet)
in
let sheet =
parse_performer x (fun performer ->
if performer <> None then { sheet with performer } else sheet)
in
try
parse_track x (fun index ->
{ sheet with tracks = parse_tracks index rem })
with Not_found -> parse sheet rem)
in
let sheet = { file; performer = None; title = None; tracks = [] } in
let sheet = parse sheet strings in
let export_track ?cue_out track =
let metadata = [("track", string_of_int track.number)] in
let maybe label value metadata =
match value with
| Some value -> (label, value) :: metadata
| None -> metadata
in
let metadata = maybe "artist" sheet.performer metadata in
let metadata = maybe "album" sheet.title metadata in
let metadata = maybe "artist" track.track_performer metadata in
let metadata = maybe "title" track.track_title metadata in
let metadata =
maybe Playlist_parser.conf_cue_in_metadata#get
(try Some (string_of_float (Hashtbl.find track.indexes 1))
with _ -> None)
metadata
in
( maybe Playlist_parser.conf_cue_out_metadata#get cue_out metadata,
Playlist_parser.get_file ?pwd sheet.file )
in
let rec export_tracks cur tracks =
match tracks with
| [] ->
assert (cur == []);
[]
| [track] -> List.rev (export_track track :: cur)
| track :: track' :: tracks ->
let cue_out =
try Some (string_of_float (Hashtbl.find track'.indexes 0))
with _ -> (
try Some (string_of_float (Hashtbl.find track'.indexes 1))
with _ -> None)
in
export_tracks (export_track ?cue_out track :: cur) (track' :: tracks)
in
export_tracks [] sheet.tracks

let _ =
Builtins_resolvers.add_playlist_parser ~format:"SCPLS" "scpls" parse_scpls

let _ = Builtins_resolvers.add_playlist_parser ~format:"CUE" "cue" parse_cue
let _ = Builtins_resolvers.add_playlist_parser ~format:"M3U" "m3u" parse_mpegurl
Loading
Loading