Skip to content

Commit

Permalink
Cue more! (#3634)
Browse files Browse the repository at this point in the history
  • Loading branch information
toots authored Jan 15, 2024
1 parent 2aa7b7a commit 008f275
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 38 deletions.
78 changes: 48 additions & 30 deletions src/libs/extra/source.liq
Original file line number Diff line number Diff line change
Expand Up @@ -262,30 +262,36 @@ end
stdlib_file = file

# Generate a CUE file for the source. This function will generate a new track in
# the file for each metadata of the source.
# the file for each metadata of the source. This function tries to map metadata to
# the appropriate CUE file standard values. You can use the `map_metadata` argument
# to add your own pre-processing. The following metadata are recognized on tracks:
# `"title"`, `"artist"`, `"album"`, `"isrc"`, and `"cue_year"`.
# @category Source / Track processing
# @flag extra
# @param filename Path where the CUE file should be written.
# @param max_length Set the maximum of track info retained by the function. \
# Infinite when `null`.
# @param ~last_tracks Only report the number of last tracks.
# @param ~title Title of the stream.
# @param ~file File where the stream is stored.
# @param ~file_type Format in which the stream is stored.
# @param ~comment Comment about the stream.
# @param ~date Year for the stream.
# @param ~year Year for the stream.
# @param ~map_metadata Function to apply to metadata before writing the CUE file (useful for pre-processing metadata).
# @param ~temp_dir Temporary directory for atomic write.
# @param ~deduplicate_using To avoid duplicate entries, duplicate metadata are \
# filtered. Set this to a list of labels to use for detecting \
# duplicated metadata.
# @param ~delete Delete the CUE files when starting if it exists.
def source.cue(
~title=null(),
~performer=null(),
~file=null(),
~file_type="mp3",
~file_type=null(),
~comment=null(),
~date=null(),
~year=null(),
~map_metadata=fun (m) -> (m : [(string*string)]),
~max_length=null(),
~last_tracks=null(),
~temp_dir=null(),
~deduplicate_using=["title", "artist", "album", "isrc", "cue_year"],
~delete=true,
filename,
s
Expand All @@ -296,6 +302,8 @@ def source.cue(
stdlib_file.remove(filename)
end

file_type = file_type ?? stdlib_file.extension(leading_dot=false, file ?? "")

is_first = ref(true)

def write(~append, entries) =
Expand Down Expand Up @@ -326,12 +334,14 @@ def source.cue(
end

# Write a tag.
def tag(~indent=0, name, (value:string?)) =
def tag(~indent=0, ~quote=true, name, (value:string?)) =
quote = if quote then fun (v) -> string.quote(v) else fun (v) -> v end

if
null.defined(value)
then
s =
"#{string.spaces(indent)}#{name} #{string.quote(null.get(value))}"
"#{string.spaces(indent)}#{name} #{quote(null.get(value))}"
w(s)
end
end
Expand All @@ -347,13 +357,11 @@ def source.cue(
"REM COMMENT",
comment
)
if
null.defined(date)
then
w(
"DATE #{(null.get(date) : int)}"
)
end
tag(
quote=false,
"REM DATE",
null.map(string.of_int, year)
)
if
null.defined(file)
then
Expand All @@ -368,8 +376,11 @@ def source.cue(
begin
let {position = p, time = t, metadata = m} = entry

w(
" TRACK #{string.of_int(digits=2, p)} AUDIO"
tag(
indent=2,
quote=false,
"TRACK",
"#{string.of_int(digits=2, p)} AUDIO"
)
tag(indent=4, "TITLE", list.assoc.nullable("title", m))
tag(indent=4, "PERFORMER", list.assoc.nullable("artist", m))
Expand All @@ -378,6 +389,13 @@ def source.cue(
"REM ALBUM",
list.assoc.nullable("album", m)
)
tag(
indent=4,
quote=false,
"REM DATE",
list.assoc.nullable("cue_year", m)
)
tag(indent=4, quote=false, "ISRC", list.assoc.nullable("isrc", m))

frames = int_of_float((t - floor(t)) * 75.)
t = int_of_float(t)
Expand All @@ -386,8 +404,11 @@ def source.cue(
m = string.of_int(digits=2, minutes)
s = string.of_int(digits=2, seconds)
f = string.of_int(digits=2, frames)
w(
" INDEX 01 #{m}:#{s}:#{f}"
tag(
indent=4,
quote=false,
"INDEX 01",
"#{m}:#{s}:#{f}"
)
end,
entries
Expand All @@ -406,25 +427,22 @@ def source.cue(
ref.incr(current_position)

if
null.defined(max_length)
null.defined(last_tracks)
then
current_entries =
null.case(
max_length,
last_tracks,
entries,
fun (max_length) ->
list.rev(list.prefix(max_length - 1, list.rev(entries())))
)
entries :=
list.mapi(
fun (pos, entry) -> entry.{position=pos + 1},
[...current_entries, entry]
fun (last_tracks) ->
list.rev(list.prefix(last_tracks - 1, list.rev(entries())))
)
entries := [...current_entries, entry]
write(append=false, entries())
else
write(append=true, [entry])
end
end

source.on_track(s, handle_metadata)
s = metadata.deduplicate(using=deduplicate_using, s)
source.on_metadata(s, handle_metadata)
end
27 changes: 24 additions & 3 deletions src/libs/source.liq
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,27 @@ def source.video(~id=null("source.video"), video) =
end

# Remove duplicate metadata in a track.
# @param ~using Labels to use to compare the metadata. Defaults to all of them \
# when `null`.
# @category Metadata
def track.metadata.deduplicate(~id=null("track.metadata.deduplicate"), t) =
def track.metadata.deduplicate(
~id=null("track.metadata.deduplicate"),
~using=null(),
t
) =
last_meta = ref([])

def f(m) =
m =
if
null.defined(using)
then
using = null.get(using)
list.filter(fun (x) -> list.mem(fst(x), using), m)
else
m
end

if
m == last_meta()
then
Expand All @@ -49,11 +65,16 @@ end

# Remove duplicate metadata in a source.
# @category Metadata
# @param ~using Labels to use to compare the metadata. Defaults to all of them \
# when `null`.
# @param ~id Source id
# @param s source
def metadata.deduplicate(~id=null("metadata.deduplicate"), s) =
def metadata.deduplicate(~id=null("metadata.deduplicate"), ~using=null(), s) =
tracks = source.tracks(s)
source(id=id, tracks.{metadata=track.metadata.deduplicate(tracks.metadata)})
source(
id=id,
tracks.{metadata=track.metadata.deduplicate(using=using, tracks.metadata)}
)
end

# Rewrite metadata on the fly using a function.
Expand Down
45 changes: 40 additions & 5 deletions tests/streams/source-cue.liq
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,26 @@ s =
title="tit",
performer="perf",
file="bla.mp3",
max_length=2,
comment=
"this is a comment",
year=2023,
last_tracks=2,
map_metadata=
fun (m) ->
list.assoc.filter_map(
fun (x, y) ->
if
y == "title2"
then
(
x,
"title 2"
)
else
(x, y)
end,
m
),
create_cuefile,
s
)
Expand All @@ -39,6 +58,8 @@ FILE "bla.mp3" MP3
TITLE "title1"
PERFORMER "artist1"
REM ALBUM "album1"
REM DATE 2021
ISRC bla
INDEX 01 00:00:00
TRACK 02 AUDIO
TITLE "title2"
Expand All @@ -54,13 +75,15 @@ FILE "bla.mp3" MP3
create_expected =
'TITLE "tit"
PERFORMER "perf"
REM COMMENT "this is a comment"
REM DATE 2023
FILE "bla.mp3" MP3
TRACK 01 AUDIO
TITLE "title2"
TRACK 02 AUDIO
TITLE "title 2"
PERFORMER "artist2"
REM ALBUM "album2"
INDEX 01 00:02:00
TRACK 02 AUDIO
TRACK 03 AUDIO
TITLE "title3"
PERFORMER "artist3"
REM ALBUM "album3"
Expand All @@ -80,11 +103,23 @@ s =
if
track_pos() == 1 and source.time(s) == 0.
then
f([("artist", "artist1"), ("album", "album1"), ("title", "title1")])
f([("artist", "artist1"), ("album", "album1"), ("title", "title1"), ("cue_year", "2021"), ("isrc", "bla")])
elsif
track_pos() == 2 and source.time(s) == 2.
then
f([("artist", "artist2"), ("album", "album2"), ("title", "title2")])
elsif
track_pos() == 2 and source.time(s) == 2.1
then
# This one should be deduplicated.
f(
[
("artist", "artist2"),
("album", "album2"),
("title", "title2"),
("foo", "bar")
]
)
elsif
track_pos() == 3 and source.time(s) == 3.
then
Expand Down

0 comments on commit 008f275

Please sign in to comment.