nice audio player
This commit is contained in:
parent
ab79098e9a
commit
b7c4360560
6
allowed_secrets.json.example.json
Normal file
6
allowed_secrets.json.example.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dev_key_f83745ft0g5rg3": {
|
||||||
|
"expiry" : "31.12.2000",
|
||||||
|
"file_root": "\\\\path\\if\\using\\windows"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app.py
17
app.py
@ -18,6 +18,7 @@ app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
|
|||||||
|
|
||||||
app.config['SECRET_KEY'] = '85c1117eb3a5f2c79f0ff395bada8ff8d9a257b99ef5e143'
|
app.config['SECRET_KEY'] = '85c1117eb3a5f2c79f0ff395bada8ff8d9a257b99ef5e143'
|
||||||
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=90)
|
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=90)
|
||||||
|
if os.environ.get('FLASK_ENV') == 'production':
|
||||||
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
|
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
|
||||||
app.config['SESSION_COOKIE_SECURE'] = True
|
app.config['SESSION_COOKIE_SECURE'] = True
|
||||||
|
|
||||||
@ -29,20 +30,23 @@ def load_allowed_secrets(filename='allowed_secrets.json'):
|
|||||||
value['expiry'] = datetime.strptime(value['expiry'], '%d.%m.%Y').date()
|
value['expiry'] = datetime.strptime(value['expiry'], '%d.%m.%Y').date()
|
||||||
return secrets
|
return secrets
|
||||||
|
|
||||||
app.config['ALLOWED_SECRETS'] = load_allowed_secrets()
|
|
||||||
|
|
||||||
def require_secret(f):
|
def require_secret(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
allowed_secrets = app.config['ALLOWED_SECRETS']
|
allowed_secrets = load_allowed_secrets()
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
def is_valid(secret_data):
|
def is_valid(secret_data):
|
||||||
|
print("is_valid:", secret_data)
|
||||||
expiry_date = secret_data.get('expiry')
|
expiry_date = secret_data.get('expiry')
|
||||||
return expiry_date and today <= expiry_date
|
is_valid = expiry_date and today <= expiry_date
|
||||||
|
print("is_valid", is_valid)
|
||||||
|
return is_valid
|
||||||
|
|
||||||
# Check if a secret was provided via GET parameter
|
# Check if a secret was provided via GET parameter
|
||||||
get_secret = request.args.get('secret')
|
get_secret = request.args.get('secret')
|
||||||
|
print("request.args:", request.args)
|
||||||
|
print("get_secret:", get_secret)
|
||||||
if get_secret is not None:
|
if get_secret is not None:
|
||||||
secret_data = allowed_secrets.get(get_secret)
|
secret_data = allowed_secrets.get(get_secret)
|
||||||
if secret_data:
|
if secret_data:
|
||||||
@ -51,13 +55,17 @@ def require_secret(f):
|
|||||||
session['secret'] = get_secret
|
session['secret'] = get_secret
|
||||||
session.permanent = True
|
session.permanent = True
|
||||||
app.config['FILE_ROOT'] = secret_data.get('file_root')
|
app.config['FILE_ROOT'] = secret_data.get('file_root')
|
||||||
|
print("session:", session['secret'])
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
# Secret provided via URL is expired or invalid
|
# Secret provided via URL is expired or invalid
|
||||||
return render_template('error.html', message="Invalid or expired secret."), 403
|
return render_template('error.html', message="Invalid or expired secret."), 403
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# If no secret provided via GET, check the session
|
# If no secret provided via GET, check the session
|
||||||
session_secret = session.get('secret')
|
session_secret = session.get('secret')
|
||||||
|
print("session_secret", session_secret)
|
||||||
if session_secret is not None:
|
if session_secret is not None:
|
||||||
secret_data = allowed_secrets.get(session_secret)
|
secret_data = allowed_secrets.get(session_secret)
|
||||||
if secret_data:
|
if secret_data:
|
||||||
@ -186,6 +194,7 @@ def generate_breadcrumbs(subpath):
|
|||||||
@app.route('/api/path/<path:subpath>')
|
@app.route('/api/path/<path:subpath>')
|
||||||
@require_secret
|
@require_secret
|
||||||
def api_browse(subpath):
|
def api_browse(subpath):
|
||||||
|
print("subpath:", subpath)
|
||||||
file_root = app.config['FILE_ROOT']
|
file_root = app.config['FILE_ROOT']
|
||||||
directory = os.path.join(file_root, subpath.replace('/', os.sep))
|
directory = os.path.join(file_root, subpath.replace('/', os.sep))
|
||||||
|
|
||||||
|
|||||||
5
requirements_transcription.txt
Normal file
5
requirements_transcription.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
openai-whisper
|
||||||
|
#https://pytorch.org/get-started/locally/
|
||||||
|
torch==2.5.1
|
||||||
|
torchvision==0.20.1
|
||||||
|
torchaudio==2.5.1 --index-url https://download.pytorch.org/whl/cu124
|
||||||
@ -91,7 +91,7 @@ function loadDirectory(subpath) {
|
|||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error loading directory:', error);
|
console.error('Error loading directory:', error);
|
||||||
document.getElementById('content').innerHTML = '<p>Error loading directory.</p>';
|
document.getElementById('content').innerHTML = '<p>Error loading directory!</p><p>'+error+'</p>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +143,7 @@ document.querySelectorAll('.play-file').forEach(link => {
|
|||||||
const fileType = this.getAttribute('data-file-type');
|
const fileType = this.getAttribute('data-file-type');
|
||||||
const relUrl = this.getAttribute('data-url');
|
const relUrl = this.getAttribute('data-url');
|
||||||
const nowPlayingInfo = document.getElementById('nowPlayingInfo');
|
const nowPlayingInfo = document.getElementById('nowPlayingInfo');
|
||||||
|
document.getElementById('audioPlayerContainer').style.display = "block"
|
||||||
|
|
||||||
// Remove the class from all file items.
|
// Remove the class from all file items.
|
||||||
document.querySelectorAll('.file-item').forEach(item => {
|
document.querySelectorAll('.file-item').forEach(item => {
|
||||||
@ -154,6 +155,7 @@ document.querySelectorAll('.play-file').forEach(link => {
|
|||||||
const idx = this.getAttribute('data-index');
|
const idx = this.getAttribute('data-index');
|
||||||
const audioPlayer = document.getElementById('globalAudio');
|
const audioPlayer = document.getElementById('globalAudio');
|
||||||
currentMusicIndex = idx !== null ? parseInt(idx) : -1;
|
currentMusicIndex = idx !== null ? parseInt(idx) : -1;
|
||||||
|
const playerButton = document.querySelector('.player-button');
|
||||||
|
|
||||||
// Add the class to the clicked file item's parent.
|
// Add the class to the clicked file item's parent.
|
||||||
this.closest('.file-item').classList.add('currently-playing');
|
this.closest('.file-item').classList.add('currently-playing');
|
||||||
@ -167,7 +169,11 @@ document.querySelectorAll('.play-file').forEach(link => {
|
|||||||
|
|
||||||
audioPlayer.pause();
|
audioPlayer.pause();
|
||||||
audioPlayer.src = ''; // Clear existing source.
|
audioPlayer.src = ''; // Clear existing source.
|
||||||
|
// only show this if it takes longer. avoid flicker
|
||||||
|
let loaderTimeout = setTimeout(() => {
|
||||||
|
playerButton.innerHTML = playIcon;
|
||||||
nowPlayingInfo.textContent = "Wird geladen...";
|
nowPlayingInfo.textContent = "Wird geladen...";
|
||||||
|
}, 500);
|
||||||
document.querySelector('footer').style.display = 'flex';
|
document.querySelector('footer').style.display = 'flex';
|
||||||
|
|
||||||
const mediaUrl = '/media/' + relUrl;
|
const mediaUrl = '/media/' + relUrl;
|
||||||
@ -175,7 +181,7 @@ document.querySelectorAll('.play-file').forEach(link => {
|
|||||||
try {
|
try {
|
||||||
// Pass the signal to the fetch so it can be aborted.
|
// Pass the signal to the fetch so it can be aborted.
|
||||||
const response = await fetch(mediaUrl, { method: 'HEAD', signal: currentFetchController.signal });
|
const response = await fetch(mediaUrl, { method: 'HEAD', signal: currentFetchController.signal });
|
||||||
|
clearTimeout(loaderTimeout); // done loading. stop timeout
|
||||||
if (response.status === 403) {
|
if (response.status === 403) {
|
||||||
nowPlayingInfo.textContent = "Fehler: Zugriff verweigert.";
|
nowPlayingInfo.textContent = "Fehler: Zugriff verweigert.";
|
||||||
window.location.href = '/'; // Redirect if forbidden.
|
window.location.href = '/'; // Redirect if forbidden.
|
||||||
@ -186,6 +192,7 @@ document.querySelectorAll('.play-file').forEach(link => {
|
|||||||
audioPlayer.src = mediaUrl;
|
audioPlayer.src = mediaUrl;
|
||||||
audioPlayer.load();
|
audioPlayer.load();
|
||||||
audioPlayer.play();
|
audioPlayer.play();
|
||||||
|
playerButton.innerHTML = pauseIcon;
|
||||||
const pathParts = relUrl.split('/');
|
const pathParts = relUrl.split('/');
|
||||||
const fileName = pathParts.pop();
|
const fileName = pathParts.pop();
|
||||||
const pathStr = pathParts.join('/');
|
const pathStr = pathParts.join('/');
|
||||||
|
|||||||
121
static/audioplayer.css
Normal file
121
static/audioplayer.css
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
.audio-player-container {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 7px;
|
||||||
|
width: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now Playing Info */
|
||||||
|
.now-playing-info {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #23313f;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-player {
|
||||||
|
--player-button-width: 4em;
|
||||||
|
--sound-button-width: 3em;
|
||||||
|
--space: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: var(--space); /* ensures spacing between controls */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the slider container fill the available space and stack its children vertically */
|
||||||
|
.slider {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The range input takes the full width of its container */
|
||||||
|
.timeline {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 0.5em;
|
||||||
|
background-color: #ccc; /* light gray background */
|
||||||
|
border-radius: 5px;
|
||||||
|
background-size: 0% 100%;
|
||||||
|
background-image: linear-gradient(#34495e, #34495e); /* progress bar */
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
appearance: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slider Thumb Styling */
|
||||||
|
/* WebKit browsers (Chrome, Safari) */
|
||||||
|
.timeline::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #34495e;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
.timeline::-moz-range-thumb {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #34495e;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internet Explorer and Edge */
|
||||||
|
.timeline::-ms-thumb {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #34495e;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Remove default track styling */
|
||||||
|
.timeline::-webkit-slider-runnable-track,
|
||||||
|
.timeline::-moz-range-track,
|
||||||
|
.timeline::-ms-track {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the time info (positioned right below the slider) */
|
||||||
|
.now-playing-info {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-button,
|
||||||
|
.sound-button {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-button {
|
||||||
|
width: var(--player-button-width);
|
||||||
|
height: var(--player-button-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sound-button {
|
||||||
|
width: var(--sound-button-width);
|
||||||
|
height: var(--sound-button-width);
|
||||||
|
}
|
||||||
157
static/audioplayer.js
Normal file
157
static/audioplayer.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
const playerButton = document.querySelector('.player-button'),
|
||||||
|
audio = document.querySelector('audio'),
|
||||||
|
timeline = document.querySelector('.timeline'),
|
||||||
|
soundButton = document.querySelector('.sound-button'),
|
||||||
|
timeInfo = document.getElementById('timeInfo'),
|
||||||
|
playIcon = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#34495e">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
pauseIcon = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#34495e">
|
||||||
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
soundIcon = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#34495e">
|
||||||
|
<path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
`,
|
||||||
|
muteIcon = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#34495e">
|
||||||
|
<path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM12.293 7.293a1 1 0 011.414 0L15 8.586l1.293-1.293a1 1 0 111.414 1.414L16.414 10l1.293 1.293a1 1 0 01-1.414 1.414L15 11.414l-1.293 1.293a1 1 0 01-1.414-1.414L13.586 10l-1.293-1.293a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
|
||||||
|
function toggleAudio () {
|
||||||
|
if (audio.paused) {
|
||||||
|
audio.play();
|
||||||
|
playerButton.innerHTML = pauseIcon;
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
playerButton.innerHTML = playIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
playerButton.addEventListener('click', toggleAudio);
|
||||||
|
|
||||||
|
let isSeeking = false;
|
||||||
|
|
||||||
|
// --- Slider (Timeline) events ---
|
||||||
|
// Mouse events
|
||||||
|
timeline.addEventListener('mousedown', () => { isSeeking = true; });
|
||||||
|
timeline.addEventListener('mouseup', () => {
|
||||||
|
isSeeking = false;
|
||||||
|
changeSeek(); // Update the audio currentTime based on the slider position
|
||||||
|
});
|
||||||
|
timeline.addEventListener('change', changeSeek);
|
||||||
|
|
||||||
|
// Touch events
|
||||||
|
timeline.addEventListener('touchstart', () => { isSeeking = true; });
|
||||||
|
timeline.addEventListener('touchend', () => {
|
||||||
|
isSeeking = false;
|
||||||
|
changeSeek();
|
||||||
|
});
|
||||||
|
timeline.addEventListener('touchcancel', () => { isSeeking = false; });
|
||||||
|
|
||||||
|
// --- When metadata is loaded, set slider range in seconds ---
|
||||||
|
audio.addEventListener('loadedmetadata', () => {
|
||||||
|
updateTimeInfo();
|
||||||
|
timeline.min = 0;
|
||||||
|
timeline.max = audio.duration;
|
||||||
|
timeline.value = 0; // Ensures the thumb starts at the left.
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Update the slider thumb position (in seconds) while playing ---
|
||||||
|
function changeTimelinePosition() {
|
||||||
|
if (!isSeeking) {
|
||||||
|
timeline.value = audio.currentTime;
|
||||||
|
const percentagePosition = (audio.currentTime / audio.duration) * 100;
|
||||||
|
timeline.style.backgroundSize = `${percentagePosition}% 100%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Seek function: directly set audio.currentTime using slider's value (in seconds) ---
|
||||||
|
function changeSeek() {
|
||||||
|
audio.currentTime = timeline.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Sound toggle ---
|
||||||
|
function toggleSound () {
|
||||||
|
audio.muted = !audio.muted;
|
||||||
|
soundButton.innerHTML = audio.muted ? muteIcon : soundIcon;
|
||||||
|
}
|
||||||
|
soundButton.addEventListener('click', toggleSound);
|
||||||
|
|
||||||
|
// --- Utility: Format seconds as mm:ss ---
|
||||||
|
function formatTime(seconds) {
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Update the time display ---
|
||||||
|
function updateTimeInfo() {
|
||||||
|
const currentTimeFormatted = formatTime(audio.currentTime);
|
||||||
|
const durationFormatted = isNaN(audio.duration) ? "00:00" : formatTime(audio.duration);
|
||||||
|
timeInfo.innerHTML = `${currentTimeFormatted} / ${durationFormatted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Update timeline, time info, and media session on each time update ---
|
||||||
|
audio.ontimeupdate = function() {
|
||||||
|
changeTimelinePosition();
|
||||||
|
updateTimeInfo();
|
||||||
|
if ('mediaSession' in navigator && typeof navigator.mediaSession.setPositionState === 'function') {
|
||||||
|
navigator.mediaSession.setPositionState({
|
||||||
|
duration: audio.duration,
|
||||||
|
playbackRate: audio.playbackRate,
|
||||||
|
position: audio.currentTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Also update media session on play (in case timeupdate lags) ---
|
||||||
|
audio.onplay = function() {
|
||||||
|
if ('mediaSession' in navigator && typeof navigator.mediaSession.setPositionState === 'function') {
|
||||||
|
navigator.mediaSession.setPositionState({
|
||||||
|
duration: audio.duration,
|
||||||
|
playbackRate: audio.playbackRate,
|
||||||
|
position: audio.currentTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Handle external seek requests (e.g. from Android or Windows system controls) ---
|
||||||
|
if ('mediaSession' in navigator) {
|
||||||
|
navigator.mediaSession.setActionHandler('seekto', function(details) {
|
||||||
|
if (details.fastSeek && 'fastSeek' in audio) {
|
||||||
|
audio.fastSeek(details.seekTime);
|
||||||
|
} else {
|
||||||
|
audio.currentTime = details.seekTime;
|
||||||
|
}
|
||||||
|
changeTimelinePosition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- When audio ends ---
|
||||||
|
audio.onended = function() {
|
||||||
|
playerButton.innerHTML = playIcon;
|
||||||
|
};
|
||||||
|
|
||||||
|
function downloadAudio() {
|
||||||
|
// Get the current audio source URL
|
||||||
|
const audioSrc = audio.currentSrc || audio.src;
|
||||||
|
if (audioSrc) {
|
||||||
|
// Create a temporary link element
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = audioSrc;
|
||||||
|
// Extract the file name from the URL, decode it, and set as download attribute
|
||||||
|
let fileName = audioSrc.split('/').pop() || 'audio';
|
||||||
|
fileName = decodeURIComponent(fileName);
|
||||||
|
a.download = fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -127,34 +127,6 @@ footer {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
footer audio {
|
|
||||||
width: 75%;
|
|
||||||
transform: scale(1.3); /* Default for other devices */
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Target Safari specifically */
|
|
||||||
@supports (-webkit-touch-callout: none) {
|
|
||||||
footer audio {
|
|
||||||
width: 60%;
|
|
||||||
transform: scale(1.6); /* Different scale for Apple devices */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.audio-player-container {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
padding: 7px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Now Playing Info */
|
|
||||||
.now-playing-info {
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #2c3e50;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal Styles */
|
/* Modal Styles */
|
||||||
#transcriptModal {
|
#transcriptModal {
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
<!-- Your CSS -->
|
<!-- Your CSS -->
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='gallery.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='gallery.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='audioplayer.css') }}">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
/* Header styles */
|
/* Header styles */
|
||||||
@ -62,12 +63,35 @@
|
|||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Global Audio Player in Footer -->
|
<!-- Global Audio Player in Footer style="display: none;"-->
|
||||||
<footer style="display: none;">
|
<footer >
|
||||||
<div class="audio-player-container">
|
<div class="audio-player-container" id="audioPlayerContainer">
|
||||||
<audio id="globalAudio" controls preload="auto">
|
<div class="audio-player">
|
||||||
|
<audio id="globalAudio">
|
||||||
Your browser does not support the audio element.
|
Your browser does not support the audio element.
|
||||||
</audio>
|
</audio>
|
||||||
|
<div class="controls">
|
||||||
|
<button class="player-button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#34495e">
|
||||||
|
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM7 8a1 1 0 012 0v4a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v4a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="slider">
|
||||||
|
<input type="range" class="timeline" max="100" value="0" step="0.1">
|
||||||
|
<div id="timeInfo" class="now-playing-info"></div>
|
||||||
|
</div>
|
||||||
|
<button class="sound-button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="#34495e">
|
||||||
|
<path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button class="sound-button" onclick="downloadAudio()">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 120.89" fill="#34495e" width="30" height="30">
|
||||||
|
<path fill-rule="evenodd" d="M84.58,47a7.71,7.71,0,1,1,10.8,11L66.09,86.88a7.72,7.72,0,0,1-10.82,0L26.4,58.37a7.71,7.71,0,1,1,10.81-11L53.1,63.12l.16-55.47a7.72,7.72,0,0,1,15.43.13l-.15,55L84.58,47ZM0,113.48.1,83.3a7.72,7.72,0,1,1,15.43.14l-.07,22q46,.09,91.91,0l.07-22.12a7.72,7.72,0,1,1,15.44.14l-.1,30h-.09a7.71,7.71,0,0,1-7.64,7.36q-53.73.1-107.38,0A7.7,7.7,0,0,1,0,113.48Z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="nowPlayingInfo" class="now-playing-info"></div>
|
<div id="nowPlayingInfo" class="now-playing-info"></div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@ -93,6 +117,7 @@
|
|||||||
<!-- Load main app JS first, then gallery JS -->
|
<!-- Load main app JS first, then gallery JS -->
|
||||||
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
<script src="{{ url_for('static', filename='app.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='gallery.js') }}"></script>
|
<script src="{{ url_for('static', filename='gallery.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='audioplayer.js') }}"></script>
|
||||||
<script>
|
<script>
|
||||||
if ('serviceWorker' in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
|||||||
@ -212,7 +212,7 @@ def process_folder(root_folder):
|
|||||||
print(f"Checked {checked_files} files. Start to transcribe {len(valid_files)} files.")
|
print(f"Checked {checked_files} files. Start to transcribe {len(valid_files)} files.")
|
||||||
|
|
||||||
print("Loading Whisper model...")
|
print("Loading Whisper model...")
|
||||||
model = whisper.load_model(model_name)
|
model = whisper.load_model(model_name, device="cuda")
|
||||||
|
|
||||||
# Use a thread pool to pre-load files concurrently.
|
# Use a thread pool to pre-load files concurrently.
|
||||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user