]> littlesong.place Git - littlesongplace.git/commitdiff
Add dreams importer UI
authorChris Fulljames <christianfulljames@gmail.com>
Sun, 18 Jan 2026 19:11:40 +0000 (14:11 -0500)
committerChris Fulljames <christianfulljames@gmail.com>
Sun, 18 Jan 2026 19:11:40 +0000 (14:11 -0500)
src/littlesongplace/__init__.py
src/littlesongplace/dreams_importer.py [new file with mode: 0644]
src/littlesongplace/templates/dreams-importer.html [new file with mode: 0644]
src/littlesongplace/templates/edit-song.html

index e07807ce4e78924d449a680f6301842bcd3f0b31..abd9426ec51ce3337225c2740d27af2122662d97 100644 (file)
@@ -11,8 +11,8 @@ from flask import Flask, render_template, request, redirect, g, session, abort,
         send_from_directory, flash, get_flashed_messages
 from werkzeug.middleware.proxy_fix import ProxyFix
 
-from . import activity, auth, colors, comments, datadir, db, jams, playlists, \
-        profiles, push_notifications, songs, users
+from . import activity, auth, colors, comments, datadir, db, dreams_importer, \
+        jams, playlists, profiles, push_notifications, songs, users
 from .logutils import flash_and_log
 
 # Logging
@@ -32,6 +32,7 @@ app.config["MAX_CONTENT_LENGTH"] = 1 * 1024 * 1024 * 1024
 app.register_blueprint(activity.bp)
 app.register_blueprint(auth.bp)
 app.register_blueprint(comments.bp)
+app.register_blueprint(dreams_importer.bp)
 app.register_blueprint(jams.bp)
 app.register_blueprint(playlists.bp)
 app.register_blueprint(profiles.bp)
diff --git a/src/littlesongplace/dreams_importer.py b/src/littlesongplace/dreams_importer.py
new file mode 100644 (file)
index 0000000..fcd07cc
--- /dev/null
@@ -0,0 +1,15 @@
+from datetime import datetime, timezone
+
+from flask import abort, Blueprint, get_flashed_messages, session, redirect, \
+        render_template, request
+
+from . import db
+from .logutils import flash_and_log
+
+bp = Blueprint("dreams-importer", __name__, url_prefix="/dreams-importer")
+
+@bp.get("")
+@bp.get("/")
+def dreams_importer():
+    return render_template("dreams-importer.html")
+
diff --git a/src/littlesongplace/templates/dreams-importer.html b/src/littlesongplace/templates/dreams-importer.html
new file mode 100644 (file)
index 0000000..374e5e0
--- /dev/null
@@ -0,0 +1,62 @@
+{% extends "base.html" %}
+{% block title %}Dreams Importer{% endblock %}
+{% block body %}
+
+<h1>dreams importer</h1>
+
+<h2>how it works</h2>
+<p>
+The Dreams importer is a script that runs on my PC. It automates interactions
+with littlesong.place, indreams.me, and Dreams running on my PS5.
+</p>
+<p>
+This means that my PC and PS5 need to be turned on and connected for the
+importer to work.  Generally, I will try to make sure it runs at least once a
+week (usually on weekends) when there are songs in the import queue.  I will
+also make sure to run it the day of the little song jam to upload any jam
+entries.
+</p>
+<p>
+If more than one weekend has passed and it still hasn't run, please send me an
+email at
+<a href="mailto:littlesongplace@gmail.com">littlesongplace@gmail.com</a>.
+</p>
+<p>
+The script performs the following sequence for each song in the queue:
+<ol>
+    <li>Connect to LSP and get the song's InDreams URL</li>
+    <li>Load the InDreams URL and click the "Play Later" button</li>
+    <li>Remix the element in Dreams</li>
+    <li>Play the song without recording to make sure all samples are loaded</li>
+    <li>Play the song again, recording the audio</li>
+    <li>Upload the song to LSP</li>
+</ol>
+</p>
+<p>
+<i>
+(For the audio nerds: The audio is recorded through a pair of high-quality USB
+audio interfaces, one connected to the PS5 and one to my PC.  Both interfaces
+operate at 24 bits/48 kHz.  The audio signal gets converted from digital to
+analog and back again, so there will be some inherent losses in this process.
+But in general it should be better than the lossy compressed audio you get from
+most HDMI capture cards or a screen recording.)
+</i>
+</p>
+
+<h2>tips for imports</h2>
+For best results:
+<ul>
+    <li>Make sure your timeline doesn't loop if it isn't supposed to.</li>
+    <li>If it <i>is</i> supposed to loop, use the Fade Out option in the
+        importer to fade out the last 10 seconds of the recording.</li>
+    <li>I have all three audio settings in Dreams turned all the way up.  Make
+        sure your song sounds correct in this configuration.</li>
+    <li>When setting the duration in the importer, make sure to leave enough
+        time for any reverb/effect trails at the end.  If in doubt, add extra
+        time - any extra silence at the end will be removed automatically.</li>
+    <li>Remember that the Dreams timeline's duration timer will be wrong if you
+        use keyframes to change the tempo - make sure to add extra time to
+        compensate.</li>
+</ul>
+
+{% endblock %}
index d360ef880c48eb2300dcd2dd790eddd91e7ca342..24951397a138f35ca183157853d92f6092c886c2 100644 (file)
@@ -4,35 +4,49 @@
 
 {% block body %}
 
-<p>
-<em>Handy Tip:</em>
-If you upload a video (e.g. a PS4/PS5 capture file), the audio will be extracted automatically!
-Most standard audio/video formats are supported - .wav, .mp3, .ogg, .mp4, etc.
-</p>
+<h2 class="mt0">{% if song %}Edit Song{% else %}Upload a New Song{% endif %}</h2>
 
 {% if song %}
 <form action="/upload-song?songid={{ song.songid }}" method="post" enctype="multipart/form-data" onsubmit="onUpload()">
-    <h2>Edit Song</h2>
 {% else %}
 <form action="/upload-song{% if eventid %}?eventid={{ eventid }}{% endif %}" method="post" enctype="multipart/form-data" onsubmit="onUpload()">
-    <h2>Upload a New Song</h2>
 {% endif %}
+    <!-- Upload Types -->
     <div class="upload-form">
-        <input type="radio" id="file" name="upload-type" value="file" onchange="selectUploadMethod()" checked />
-        <label for="file">Upload a song from my device</label><br/>
+        <input type="radio" id="file-radio" name="upload-type" value="file" onchange="selectUploadMethod()" checked />
+        <label for="file-radio">Upload a song from my device</label><br/>
     </div>
     <div class="upload-form">
-        <input type="radio" id="yt" name="upload-type" value="yt" onchange="selectUploadMethod()"/>
-        <label for="yt">Import a song from YouTube</label>
+        <input type="radio" id="dreams-radio" name="upload-type" value="dreams" onchange="selectUploadMethod()"/>
+        <label for="dreams-radio">Import a song using the <a href="/dreams-importer">Dreams Importer</a></label> 
     </div>
-    <div class="upload-form" id="audio-file">
+    <div class="upload-form">
+        <input type="radio" id="yt-radio" name="upload-type" value="yt" onchange="selectUploadMethod()"/>
+        <label for="yt-radio">Import a song from YouTube</label>
+    </div>
+
+    <!-- Song Metadata -->
+    <div class="upload-form" id="song-file-container">
         <label for="song-file">{% if song %}Replace {% endif %}Audio File</label><br>
-        <input type="file" name="song-file" id="song-file" {% if not song %}required{% endif %}>
+        <input type="file" name="song-file" id="song-file" {% if not song %}required{% endif %}><br/>
+        <small>
+            <em>Handy Tip:</em>
+            If you upload a video (e.g. a PS4/PS5 capture file), the audio will be extracted automatically!
+            Most standard audio/video formats are supported - .wav, .mp3, .ogg, .mp4, etc.
+        </small>
     </div>
-    <div class="upload-form" id="yt-url" hidden>
-        <label for="song-url">YouTube URL</label><br>
+    <div class="upload-form" id="song-url-container" hidden>
+        <label for="song-url" id="song-url-label">YouTube URL</label><br>
         <input type="url" name="song-url" id="song-url">
     </div>
+    <div class="upload-form" id="song-duration-container">
+        <label>Song Duration (Minutes:Seconds)<br>
+            <input type="text" pattern="\d+:\d\d" name="song-duration" value="3:00" /></label>
+    </div>
+    <div class="upload-form" id="fade-out-container">
+        <label>Fade Out (for Loops)<br>
+            <input type="checkbox" name="fade-out" style="margin: 10px"/></label>
+    </div>
     <div class="upload-form">
         <label for="title">Title</label><br>
         <input type="text" name="title" id="song-title" value="{{ song.title }}" maxlength="80" required>
@@ -50,11 +64,7 @@ Most standard audio/video formats are supported - .wav, .mp3, .ogg, .mp4, etc.
         <input type="text" name="collabs" placeholder="@fren_user, John Doe, ..." value="{{ ", ".join(song.collaborators) }}" maxlength="350">
     </div>
     <div class="upload-form">
-        {% if song %}
-        <input type="submit" value="Update" />
-        {% else %}
-        <input type="submit" value="Upload" />
-        {% endif %}
+        <input type="submit" value="Submit" />
         <p id="uploading" hidden>uploading...</p>
     </div>
 </form>
@@ -77,21 +87,46 @@ function onUpload(event) {
 
 // Toggle YouTube import/File upload
 function selectUploadMethod() {
-    if (document.getElementById("file").checked) {
+    if (document.getElementById("file-radio").checked) {
         // Show audio file upload button
-        document.getElementById("yt-url").hidden = true;
+        document.getElementById("song-url-container").hidden = true;
         document.getElementById("song-url").required = false;
 
-        document.getElementById("audio-file").hidden = false;
+        document.getElementById("song-file-container").hidden = false;
         document.getElementById("song-file").required = {% if song %}false{% else %}true{% endif %};
+
+        document.getElementById("song-duration-container").hidden = true;
+        document.getElementById("song-duration-container").required = false;
+
+        document.getElementById("fade-out-container").hidden = true;
     }
-    else {
+    else if (document.getElementById("yt-radio").checked) {
         // Show youtube import URL box
-        document.getElementById("yt-url").hidden = false;
+        document.getElementById("song-url-container").hidden = false;
         document.getElementById("song-url").required = {% if song %}false{% else %}true{% endif %};
+        document.getElementById("song-url-label").innerText = "YouTube URL"
 
-        document.getElementById("audio-file").hidden = true;
+        document.getElementById("song-file-container").hidden = true;
         document.getElementById("song-file").required = false;
+
+        document.getElementById("song-duration-container").hidden = true;
+        document.getElementById("song-duration-container").required = false;
+
+        document.getElementById("fade-out-container").hidden = true;
+    }
+    else if (document.getElementById("dreams-radio").checked) {
+        // Show dreams import URL box
+        document.getElementById("song-url-container").hidden = false;
+        document.getElementById("song-url").required = {% if song %}false{% else %}true{% endif %};
+        document.getElementById("song-url-label").innerText = "InDreams Element URL (element must be public!)"
+
+        document.getElementById("song-file-container").hidden = true;
+        document.getElementById("song-file").required = false;
+
+        document.getElementById("song-duration-container").hidden = false;
+        document.getElementById("song-duration-container").required = true;
+
+        document.getElementById("fade-out-container").hidden = false;
     }
 }