Skip to content

Commit

Permalink
feat: add support for adjusting a/v sync (#489)
Browse files Browse the repository at this point in the history
Added the possibility for the admin to fix the A/V synchronization.

- CLI Parameter: --avsync (Use avsync (in seconds) if the audio and
video streams are out of sync. (negative = advances audio | positive =
delays audio))
- User preference: avsync

I haven't test it with CDG/MP3 files

Now I'll create a "how -to" on how the user can adjust the a/v sync.
  • Loading branch information
lvmasterrj authored Feb 6, 2025
1 parent aa09299 commit 40f372f
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 6 deletions.
1 change: 1 addition & 0 deletions pikaraoke/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ def main():
bg_video_path=args.bg_video_path,
disable_score=args.disable_score,
limit_user_songs_by=args.limit_user_songs_by,
avsync=args.avsync,
config_file_path=args.config_file_path,
)

Expand Down
13 changes: 11 additions & 2 deletions pikaraoke/karaoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def __init__(
disable_bg_video=False,
disable_score=False,
limit_user_songs_by=0,
avsync=0,
config_file_path="config.ini",
):
logging.basicConfig(
Expand Down Expand Up @@ -161,6 +162,7 @@ def __init__(
self.limit_user_songs_by = (
self.get_user_preference("limit_user_songs_by") or limit_user_songs_by
)
self.avsync = self.get_user_preference("avsync") or avsync
self.url_override = url
self.url = self.get_url()

Expand Down Expand Up @@ -458,7 +460,10 @@ def play_file(self, file_path, semitones=0):
logging.info(f"Playing file: {file_path} transposed {semitones} semitones")

requires_transcoding = (
semitones != 0 or self.normalize_audio or is_transcoding_required(file_path)
semitones != 0
or self.normalize_audio
or is_transcoding_required(file_path)
or self.avsync != 0
)

logging.debug(f"Requires transcoding: {requires_transcoding}")
Expand Down Expand Up @@ -493,7 +498,11 @@ def play_file(self, file_path, semitones=0):
else:
self.kill_ffmpeg()
ffmpeg_cmd = build_ffmpeg_cmd(
fr, semitones, self.normalize_audio, self.complete_transcode_before_play
fr,
semitones,
self.normalize_audio,
self.complete_transcode_before_play,
self.avsync,
)
self.ffmpeg_process = ffmpeg_cmd.run_async(pipe_stderr=True, pipe_stdin=True)

Expand Down
6 changes: 6 additions & 0 deletions pikaraoke/lib/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ def parse_pikaraoke_args():
default="0",
required=False,
),
parser.add_argument(
"--avsync",
help="Use avsync (in seconds) if the audio and video streams are out of sync. (negative = advances audio | positive = delays audio)",
default="0",
required=False,
),
parser.add_argument(
"--config-file-path",
help=f"Path to a config file to load settings from. Config file settings are set in the web interface or manually edited and will override command line arguments. Default {default_config_file_path}",
Expand Down
17 changes: 14 additions & 3 deletions pikaraoke/lib/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ def get_media_duration(file_path):
return None


def build_ffmpeg_cmd(fr, semitones=0, normalize_audio=True, buffer_fully_before_playback=False):
def build_ffmpeg_cmd(
fr, semitones=0, normalize_audio=True, buffer_fully_before_playback=False, avsync=0
):
avsync = float(avsync)
# use h/w acceleration on pi
default_vcodec = "h264_v4l2m2m" if supports_hardware_h264_encoding() else "libx264"
# just copy the video stream if it's an mp4 or webm file, since they are supported natively in html5
Expand All @@ -24,13 +27,21 @@ def build_ffmpeg_cmd(fr, semitones=0, normalize_audio=True, buffer_fully_before_

# copy the audio stream if no transposition/normalization, otherwise reincode with the aac codec
is_transposed = semitones != 0
acodec = "aac" if is_transposed or normalize_audio else "copy"
acodec = "aac" if is_transposed or normalize_audio or avsync != 0 else "copy"

input = ffmpeg.input(fr.file_path)
audio = input.audio

# If avsync is set, delay or trim the audio stream
if avsync > 0:
audio = audio.filter("adelay", f"{avsync * 1000}|{avsync * 1000}") # delay
elif avsync < 0:
audio = audio.filter("atrim", start=-avsync) # trim

# The pitch value is (2^x/12), where x represents the number of semitones
pitch = 2 ** (semitones / 12)

audio = input.audio.filter("rubberband", pitch=pitch) if is_transposed else input.audio
audio = audio.filter("rubberband", pitch=pitch) if is_transposed else audio
# normalize the audio
audio = audio.filter("loudnorm", i=-16, tp=-1.5, lra=11) if normalize_audio else audio

Expand Down
1 change: 1 addition & 0 deletions pikaraoke/routes/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def info():
disable_score=k.disable_score,
hide_url=k.hide_url,
limit_user_songs_by=k.limit_user_songs_by,
avsync=k.avsync,
hide_notifications=k.hide_notifications,
hide_overlay=k.hide_overlay,
normalize_audio=k.normalize_audio,
Expand Down
9 changes: 8 additions & 1 deletion pikaraoke/templates/info.html
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ <h4 style="margin-top: 20px">{# MSG: Title text for the server settings section
</label>
</div>

<div class="user-preference-container">
<input id="pref-avsync" class="user-preference-input" type="number" step="0.1" min="-10" max="10" data-pref="avsync" data-start-value="{{ avsync }}" value="{{ avsync }}" />
<label class="label">
{# MSG: Numberbox label for audio video synchronization #} {% trans %}Fix the audio and video synchronization in seconds (negative = advances audio | positive = delays audio){% endtrans %}
</label>
</div>

<div class="user-preference-container">
<input id="pref-limit-user-songs-by" class="user-preference-input" type="number" min="0" max="99" data-pref="limit_user_songs_by" data-start-value="{{ limit_user_songs_by }}" value="{{ limit_user_songs_by }}" />
<label class="label">
Expand All @@ -255,7 +262,7 @@ <h4 style="margin-top: 20px">{# MSG: Title text for the server settings section
</label>
</div>

<p class="help">{# MSG: Help text explaining when videos will be transcoded #} {% trans %}* Videos are only transcoded when: normalization is on, a song is transposed, playing a CDG/MOV/AVI/MKV file. Most unmodified MP4 files will not need to be transcoded.{% endtrans %} </p>
<p class="help">{# MSG: Help text explaining when videos will be transcoded #} {% trans %}* Videos are only transcoded when: normalization is on, audio/video synchronization not zero, a song is transposed, playing a CDG/MOV/AVI/MKV file. Most unmodified MP4 files will not need to be transcoded.{% endtrans %} </p>

<div style="margin-top: 20px">
{# MSG: Text for the link where the user can clear all user preferences #}
Expand Down

0 comments on commit 40f372f

Please sign in to comment.