savonet / liquidsoap

Liquidsoap is a statically typed scripting general-purpose language with dedicated operators and backend for all thing media, streaming, file generation, automation, HTTP backend and more.
http://liquidsoap.info
GNU General Public License v2.0
1.39k stars 128 forks source link

Metadata Issue in 1.4.1 #1541

Open SkillenUK opened 3 years ago

SkillenUK commented 3 years ago

Describe the bug I've moved from Ubuntu Server 12.04 (which I think ran Liquidsoap 1.1.1) to Ubuntu Server 20.04, which now has Liquidsoap 1.4.1 in the repository. I have never managed to get Liquidsoap installed in a way which I am happy with through OPAM or using the script so I stick to the version package manager, to be honest I was happy with 1.1.1.

I am running the same script which I ran in Liquidsoap 1.1.1 which worked as I expected it to. Now in Liquidsoap 1.4.1, when the server starts up the playlist is loaded and the metadata from the first file in my playlist comes through to the output.icecast object as expected.

When the track finishes and the playlist (autodj) moves to the next file, the metadata from the first track remains but another "REPLAY: " is appended to the start as the append_autodj(m) function is firing again. This continues this way, continuing to display the metadata for the first track with multiple appends at the start for every new track in the playlist.

If a live harbor connects then the correct metadata comes through from that, but when the live finished and the playlist (autodj) comes back on then the incorrect metadata from that first track returns. I have tried connecting through Telnet after it has been playing for a while (so has cycled through several tracks in the playlist) and the correct metadata can't be seen in any of the last played positions.

To Reproduce I've tried to provide a stripped down script containing the elements that could be causing the issue...

# Input harbor
djlive = input.harbor("live",port=8010,password="******")
djlivebuffer10 = input.harbor("livebuffer10",port=8010,password="******",buffer=10.,max=20.)

# Append live to the metadata
def append_live(m) =
  title = m["artist"]
  [("artist","LIVE: #{title}")]
end
djlive = map_metadata(append_live, djlive)
djlivebuffer10 = map_metadata(append_live, djlivebuffer10)

# Make safe then strip blank to give 10 seconds without auto DJ kicking in if the DJ drops out
live = strip_blank(max_blank=10.,mksafe(fallback(track_sensitive=false, [djlive,djlivebuffer10])))

# Auto dj playlist input
autodj = playlist(mode="randomize",reload=1,reload_mode="rounds","/etc/liquidsoap/autodj/autodj.m3u")

# Append replay to the metadata
def append_autodj(m) =
  title = m["artist"]
  [("artist","REPLAY: #{title}")]
end
autodj = map_metadata(append_autodj, autodj)

# Define transitions for changing between live and auto dj
def tolive(old,new) =
  sequence([fade.final(duration=1.,old),new])
end

def toauto(old,new)
  # Skip to next track on playlist
  source.skip(new)
  sequence(merge=true,[fade.initial(duration=2.,new)])
end

# Set the fallback between live and auto
testoutput = mksafe(fallback(track_sensitive=false, transitions=[tolive,toauto], [live,autodj]))

# Test transcoder : MP3 128 kbps stereo
output.icecast(
  %mp3(bitrate=128),
  mount="/test128",
  host="localhost", port=8000, password="******",
  name="Test Stream", description="128kbps MP3 stream", url="", genre="Music",
  testoutput)

Expected behavior When the fallback is playing the playlist (autodj), I would expect the metadata for the currently playing item to be passed through to the output.icecast object.

Version details

Install method Ubuntu package manager (apt-get).

Common issues I have searched around, including in this issues log and I have been unable to find someone with the same issue. If you can point to an issue in my script or provide me with a workaround it would be good. I would not like to need to install a newer version through OPAM (it just seems messy that way). Hopefully the package in Ubuntu will be updated to a version which does not contain the issue soon. I am using the same script I had in v1.1.1.

toots commented 3 years ago

Hi! There's a good chance the issue is already fixed since 1.4.2: https://github.com/savonet/liquidsoap/issues/1115

SkillenUK commented 3 years ago

OK thanks, I've tackled OPAM again and managed to get 1.4.4 installed. The issue is resolved in that version. I can't seem to get the daemon script working with the OPAM install, I get the "Failed to start liquidsoap.service: Unit liquidsoap.service not found." message when I try to start the service after running the script.

SkillenUK commented 3 years ago

OK, so I'm still having the issue in 1.4.4, it appeared to be working at first but the issue is definately there. I have done some more testing and if I add a line in to mksafe the autodj object then output that directly then the metadata works. The issue is happening after someone has connected to one of the input.harbor objects. I have restarted the server and connected to liquidsoap via telnet then used the .skip command to play the next track and the metadata comes through correctly on all outputs. But after connection to the input.harbor, the autodj skips to the next track but the old metadata is retained on the output which has gone through the fallback routine. The append_autodj routine is running again and I am getting an extra "REPLAY: " added to the start. Maybe it's related to the transition where the command to skip to the next track is happening.

Now I've tested this some more I'm convinced that you will be able to reproduce the problem with the script above. Let me know if you need anything else, thanks.

toots commented 3 years ago

Ok, thanks for the effort. I'll try to reproduce.

toots commented 3 years ago

I can confirm the issue..

toots commented 3 years ago

@SkillenUK I would suggest to skip the track when leaving the playlist instead of entering it and also to do it in a asynchronous task in order to not confuse the switch:

# Define transitions for changing between live and auto dj
def tolive(old,new) =
  def skip() =
    source.skip(old)
    (-1.)
  end
  add_timeout(0., skip)
  sequence([fade.final(duration=1.,old),new])
end

def toauto(old,new)
  sequence(merge=true,[fade.initial(duration=2.,new)])
end

Let me know if this works for you.

SkillenUK commented 3 years ago

Thanks for your help. The correct metadata comes through with the solution above. On the first fallback I still get one extra append on the autodj source, i.e. 'REPLAY: REPLAY: '. Subsequent fallbacks appear to remove the second append then add it again afterwards, so I consistently get one duplicate. I have a Python script fetching the metadata and processing it before storing it in the database for use on my site, so as long as the behaviour is consistent then it's not an issue for me.</p> <p>Obviously the original script would start at the beginning of an autodj entry when the harbor disconnects, where as this one will be part way through a file as it's now skipping on connection. I'll play with it a bit more, thanks again.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/toots"><img src="https://avatars.githubusercontent.com/u/871060?v=4" />toots</a> commented <strong> 3 years ago</strong> </div> <div class="markdown-body"> <p>Ok thanks. I'll look at this shortly.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/SkillenUK"><img src="https://avatars.githubusercontent.com/u/25122561?v=4" />SkillenUK</a> commented <strong> 3 years ago</strong> </div> <div class="markdown-body"> <p>Hi, I just wondered if there was any update on this issue? Thanks :)</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/toots"><img src="https://avatars.githubusercontent.com/u/871060?v=4" />toots</a> commented <strong> 3 years ago</strong> </div> <div class="markdown-body"> <p>Hi @SkillenUK. We're pushed some fixes on the <code>main</code> branch that could very well have fixed this one as well. Would you be able to test it? You would need to migrate your script to liquidsoap <code>2.0.0</code>. This page has some tips for it: <a href="https://www.liquidsoap.info/doc-dev/migrating.html">https://www.liquidsoap.info/doc-dev/migrating.html</a></p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/SkillenUK"><img src="https://avatars.githubusercontent.com/u/25122561?v=4" />SkillenUK</a> commented <strong> 3 years ago</strong> </div> <div class="markdown-body"> <p>Thanks, I will have a go at migrating the script and I'll try to find some down time when I am free to upgrade it. I'll let you know when I have done it.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/SkillenUK"><img src="https://avatars.githubusercontent.com/u/25122561?v=4" />SkillenUK</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>Sorry for the delay, I had initially tried a while back on my production environment but it was taking me too long to get the script working so I needed to roll it back. Setting up a dev environment then migrating my script for 2.0.0 needed some time.</p> <p>In 2.0.2 the metadata for my autodj playlist is coming through as 'REPLAY: - Unknown' on the output. If I output the autodj source directly (i.e. shortcut the metadata mapping and fallback routines) then I get the correct metadata from the mp3. So this is not my original issue but also not the expected behavior.</p> <p>When a streaming client connects to the harbor, Liquidsoap is exiting with the message 'Segmentation fault (core dumped)'. Checking the logs this is happening as soon as my transition fires. I have tried to use a similar transition but after playing with it for a while I can't get it to work. Therefore I need to resolve this before I can test for the original problem. My code is as follows...</p> <p>Original (1.4.1):</p> <pre><code>def tolive(old,new) = sequence([fade.final(duration=1.,old),new]) end</code></pre> <p>New (2.0.2):</p> <pre><code>def tolive(a,b) sequence([fade.out(duration=1., a),(b:source)]) end</code></pre> <p>I haven't worked out how to do my transition back to the auto dj in 2.0.2 (as I had it before) so I have left this out completely for now. If anyone can advise me with getting the transition working then, I will be able to check if the original metadata issue still exists. </p> <p>If you need me to post my full converted script then let me know. Thanks :)</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/toots"><img src="https://avatars.githubusercontent.com/u/871060?v=4" />toots</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>Hi,</p> <p>I think we need to file a different issue as this one is already overloaded and, I believe, applies to <code>1.4.1</code>.</p> <p><del>The issue with metadata will need more of your script. We're gonna need to understand how this <code>REPLAY: ...</code> metadata is being created.</del></p> <p>The segfault, however, is worrysome. Could you upload the core dump somewhere? How did you install the liquidsoap binary? Can you also upload the binary itself along with the coredump?</p> <p>Thanks for your help with those!</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/toots"><img src="https://avatars.githubusercontent.com/u/871060?v=4" />toots</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>My bad I see the script above. I'll try to reproduce that part. Meanwhile, this coredump+binary would be greatly appreciated1</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/SkillenUK"><img src="https://avatars.githubusercontent.com/u/25122561?v=4" />SkillenUK</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>I have installed it through OPAM in Ubuntu which gave me 2.0.2. I can still send the binary if you need it. Here is the script migrated for 2.0.0 so that you can try to replicate exactly what I have, I've stripped a couple of unnesacary parts out as well as changing the obvious stuff such as passwords and stream name etc...</p> <pre><code># Allow sudo run settings.init.allow_root.set(true) # Set pidfile settings.init.daemon.pidfile.path.set("/etc/liquidsoap/daemon/pid.txt") # Enable harbor for any external connection settings.harbor.bind_addrs.set(["0.0.0.0"]) # Enable Telnet settings.server.telnet.bind_addr.set("127.0.0.1") settings.server.telnet.port.set(8030) settings.server.telnet.set(true) # Set log file path settings.log.file.path.set("/var/log/liquidsoap/radioref.log") # Set log level (put up to 4 for debugging) #log.level.set(3) log.level.set(4) # St the log file permission so that users can read the file settings.log.file.perms.set(755) # Enable file log, disable stdout log log.file.set(true) log.stdout.set(false) # Set the ssl certs for the harbor settings.harbor.ssl.certificate.set("/etc/ssl/certs/filename.crt") settings.harbor.ssl.private_key.set("/etc/ssl/private/filename.key") # Define a function to handle harbor authentication def dj_auth(params) user = params.user password = params.password ret = get_process_lines("php liqauth.php '#{user}' '#{password}'") ret = list.hd(default="",ret) if ret == "true" then true else false end end # Main DJ live harbor using ssl and website account authentication djlivessl = input.harbor.ssl("live",port=8020,auth=dj_auth,replay_metadata=true) # DJ live input using direct connection to liquidsoap djlive = input.harbor("live",port=8010,password="*****",replay_metadata=true) # Append live to the metadata def append_live(m) title = m["artist"] [("artist","LIVE: #{title}")] end djlivessl = map_metadata(append_live, djlivessl) djlive = map_metadata(append_live, djlive) # Set the fallback for the live streams keeping the stream alive for 10 seconds if it drops out live = blank.strip(max_blank=10.,mksafe(fallback(track_sensitive=false, [(djlivessl:source),(djlive:source)]))) # Auto dj playlist input autodj = playlist(mode="randomize",reload=1,reload_mode="rounds","autodj.m3u") autodjdirect = mksafe(autodj) # Append replay to the metadata def append_autodj(m) = title = m["artist"] [("artist","REPLAY: #{title}")] end autodj = map_metadata(append_autodj, autodj) # Transition to the live stream def to_live(a,b) sequence([fade.out(duration=1., a),(b:source)]) end # Set the fallback between live and auto mainoutput = mksafe(fallback(track_sensitive=false, transitions=[to_live], [(live:source),(autodj:source)])) # MP3 128 kbps stereo output.icecast( %mp3(bitrate=128), mount="/streamname128", host="localhost", port=8000, password="*****", name="Name", description="128kbps MP3 stream", url="https://www.test.com", genre="Dance Music", mainoutput) # Output the auto dj directly so that we can get the metadata, this is due to a bug in Liquidsoap output.icecast( %mp3(bitrate=128), mount="/streamnameautodj", host="localhost", port=8000, password="*****", name="Name Auto DJ", description="128kbps MP3 stream", url="https://www.test.com", genre="Dance Music", autodjdirect) # Output the stream to the downloads folder output.file( %mp3(bitrate=128, id3v2=true), reopen_on_metadata=false, "/var/www/download/shows/%Y-%m-%d-%H_%M_%S_ssldjlive.mp3", djlivessl, fallible=true) output.file( %mp3(bitrate=128, id3v2=true), reopen_on_metadata=false, "/var/www/download/shows/%Y-%m-%d-%H_%M_%S_djlive.mp3", djlive, fallible=true)</code></pre> <p>I will need to find out how to do the coredump but I will look in to it and hopefully have it soon. Thanks.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/SkillenUK"><img src="https://avatars.githubusercontent.com/u/25122561?v=4" />SkillenUK</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>After Googling core dumps I've taken the following from the Apport log. I don't think it's telling us much but is this what you wanted?</p> <pre><code>ERROR: apport (pid 1452) Mon Jan 17 08:39:11 2022: called for pid 1374, signal 11, core limit 0, dump mode 1 ERROR: apport (pid 1452) Mon Jan 17 08:39:11 2022: executable: /home/ubuntu/.opam/4.10.0/bin/liquidsoap (command line "/home/ubuntu/.opam/4.10.0/bin/liquidsoap /etc/liquidsoap/radioref2_0.liq") ERROR: apport (pid 1452) Mon Jan 17 08:39:11 2022: executable does not belong to a package, ignoring</code></pre> <p>The same messages are repeated each time that I have tried running Liquidsoap and connecting a streaming client. For the binary, do you want me to attach the Liquidsoap file from the Opam bin folder, or do you want the entire folder to be uploaded in a tar.gz archive? Thanks.</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/toots"><img src="https://avatars.githubusercontent.com/u/871060?v=4" />toots</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>The core dump can be used with <code>gdb</code> to extract a stack trace of the segfault, letting us know which part of the code triggered it.</p> <p>BTW, what version of <code>ocaml-lame</code> are you using? We fixed an issue that was triggering segfault with release 0.3.5: <a href="https://github.com/savonet/ocaml-lame/releases/tag/v0.3.5">https://github.com/savonet/ocaml-lame/releases/tag/v0.3.5</a></p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/SkillenUK"><img src="https://avatars.githubusercontent.com/u/25122561?v=4" />SkillenUK</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>The version of lame is 0.3.5 so this should be fine. The output from gdb when connecting a streaming client to the harbor was...</p> <pre><code>Starting program: /home/ubuntu/.opam/4.10.0/bin/liquidsoap /etc/liquidsoap/radioref2_0.liq [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [Detaching after vfork from child process 1057] [New Thread 0x7ffff132b700 (LWP 1059)] [New Thread 0x7ffff0b2a700 (LWP 1060)] [New Thread 0x7ffff0329700 (LWP 1061)] [New Thread 0x7fffef8e4700 (LWP 1072)] [New Thread 0x7fffef0e3700 (LWP 1073)] [New Thread 0x7fffee8e2700 (LWP 1074)] [New Thread 0x7fffee0e1700 (LWP 1075)] [New Thread 0x7fffed8e0700 (LWP 1076)] [New Thread 0x7fffed0df700 (LWP 1078)] Thread 5 "liquidsoap" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7fffef8e4700 (LWP 1072)] 0x0000555555cfb92f in caml_taglib_tag_set_string (t=140737241200416, name=93825003678688, v=140737241198160) at taglib_stubs.cc:448 448 taglib_stubs.cc: No such file or directory.</code></pre> <p>This made me realise that I hadn't created the folder for the output.file path on the VM, so apologies this was my fault. I would probably expect that Liquidsoap would handle this kind of error elegantly and put a useful message in the log, which may be worth considering.</p> <p>After creating the folder I am still having an error when connecting the streaming client to the harbor. It seems to get further but Liquidsoap is now exiting with the error "Segmentation fault". The log file is showing...</p> <pre><code>2022/01/20 08:11:10 [harbor:4] New client on port 8010: 192.168.238.1 2022/01/20 08:11:10 [harbor:4] Method: PUT, uri: /live, protocol: HTTP/1.1 2022/01/20 08:11:10 [harbor:4] Header: Host, value: 192.168.238.5:8010. 2022/01/20 08:11:10 [harbor:4] Header: User-Agent, value: butt 0.1.32. 2022/01/20 08:11:10 [harbor:4] Header: Content-Type, value: audio/mpeg. 2022/01/20 08:11:10 [harbor:4] Header: ice-name, value: no name. 2022/01/20 08:11:10 [harbor:4] Header: ice-public, value: 0. 2022/01/20 08:11:10 [harbor:4] Header: ice-audio-info, value: ice-bitrate=128;ice-channels=2;ice-samplerate=44100. 2022/01/20 08:11:10 [harbor:4] Header: Expect, value: 100-continue. 2022/01/20 08:11:10 [harbor:4] Client logged in. 2022/01/20 08:11:10 [harbor:4] PUT (source) request on /live. 2022/01/20 08:11:10 [harbor:4] Adding source on mountpoint "/live" with type "audio/mpeg". 2022/01/20 08:11:10 [decoder:4] Available decoders: 2022/01/20 08:11:10 [decoder:4] MAD (priority: 1) 2022/01/20 08:11:10 [decoder:4] Selected decoder MAD for mime-type audio/mpeg with expected content {audio=pcm(stereo),video=none,midi=none} 2022/01/20 08:11:10 [threads:4] Created thread "harbor source feeding" (2 total). 2022/01/20 08:11:10 [input.harbor_0:3] Decoding...</code></pre> <p>I'm one step further but I'm still unsure as to why the streaming client is stopping Liquidsoap when it connects.</p> <p>Thank you for your help on this :)</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/toots"><img src="https://avatars.githubusercontent.com/u/871060?v=4" />toots</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>Thanks for this update. What version of <code>ocaml-taglib</code> are you using?</p> </div> </div> <div class="comment"> <div class="user"> <a rel="noreferrer nofollow" target="_blank" href="https://github.com/stale[bot]"><img src="https://avatars.githubusercontent.com/in/1724?v=4" />stale[bot]</a> commented <strong> 2 years ago</strong> </div> <div class="markdown-body"> <p>This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.</p> </div> </div> <div class="page-bar-simple"> </div> <div class="footer"> <ul class="body"> <li>© <script> document.write(new Date().getFullYear()) </script> Githubissues.</li> <li>Githubissues is a development platform for aggregating issues.</li> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"></script> <script src="/githubissues/assets/js.js"></script> <script src="/githubissues/assets/markdown.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/highlight.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/languages/go.min.js"></script> <script> hljs.highlightAll(); </script> </body> </html>