Function bodies 189 total
findFFmpeg function · javascript · L10-L64 (55 LOC)main.js
function findFFmpeg() {
// First try to find via 'where' command (searches PATH)
try {
const result = require('child_process').execSync('where ffmpeg', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
if (result) {
const firstPath = result.split('\n')[0].trim();
if (firstPath) return firstPath;
}
} catch (e) { /* not in PATH */ }
// Search common locations
const possible = [
path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WinGet', 'Links', 'ffmpeg.exe'),
'C:\\ffmpeg\\bin\\ffmpeg.exe',
path.join(process.env.ProgramFiles || '', 'ffmpeg', 'bin', 'ffmpeg.exe'),
path.join(process.env.ProgramFiles || '', 'ffmpeg', 'ffmpeg.exe'),
];
// Also search WinGet packages folder recursively
const wingetPkgs = path.join(process.env.LOCALAPPDATA || '', 'Microsoft', 'WinGet', 'Packages');
try {
if (fs.existsSync(wingetPkgs)) {
const dirs = fs.readdirSync(wingetPkgs).filter(d => d.toLowerCase().includes('ffmpeg'createWindow function · javascript · L66-L134 (69 LOC)main.js
function createWindow() {
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
minWidth: 800,
minHeight: 600,
title: 'Accessible Studio',
icon: path.join(__dirname, 'src', 'assets', 'icon.png'),
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
webSecurity: false, // Allow file:// images on canvas without tainting (needed for photo editor)
},
});
mainWindow.loadFile(path.join(__dirname, 'src', 'index.html'));
// Build accessible menu
const menuTemplate = [
{
label: '&File',
submenu: [
{ label: '&New Project', accelerator: 'CmdOrCtrl+N', click: () => mainWindow.webContents.send('menu-action', 'new-project') },
{ label: '&Open Project', accelerator: 'CmdOrCtrl+O', click: () => handleOpenProject() },
{ label: '&Save Project', accelerator: 'CmdOrCtrl+S', click: () => mainWindow.webContents.send('menu-action', 'save-phandleImportMedia function · javascript · L137-L150 (14 LOC)main.js
async function handleImportMedia() {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Import Media Files',
filters: [
{ name: 'Video Files', extensions: ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'mpeg', 'mpg', 'm4v'] },
{ name: 'Image Files', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff', 'svg'] },
{ name: 'All Files', extensions: ['*'] },
],
properties: ['openFile', 'multiSelections'],
});
if (!result.canceled && result.filePaths.length > 0) {
mainWindow.webContents.send('files-imported', result.filePaths);
}
}handleImportAudio function · javascript · L152-L164 (13 LOC)main.js
async function handleImportAudio() {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Import Audio Files',
filters: [
{ name: 'Audio Files', extensions: ['mp3', 'wav', 'ogg', 'flac', 'aac', 'wma', 'm4a'] },
{ name: 'All Files', extensions: ['*'] },
],
properties: ['openFile', 'multiSelections'],
});
if (!result.canceled && result.filePaths.length > 0) {
mainWindow.webContents.send('audio-imported', result.filePaths);
}
}handleOpenProject function · javascript · L166-L176 (11 LOC)main.js
async function handleOpenProject() {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Open Project',
filters: [{ name: 'Accessible Studio Project', extensions: ['asproj'] }],
properties: ['openFile'],
});
if (!result.canceled && result.filePaths.length > 0) {
const data = fs.readFileSync(result.filePaths[0], 'utf-8');
mainWindow.webContents.send('project-loaded', JSON.parse(data));
}
}getSourceContent function · javascript · L272-L290 (19 LOC)main.js
async function getSourceContent() {
if (inputExt === 'docx') {
try {
const mammoth = require('mammoth');
const htmlResult = await mammoth.convertToHtml({ path: inputPath });
const textResult = await mammoth.extractRawText({ path: inputPath });
return { html: htmlResult.value, text: textResult.value };
} catch (e) {
throw new Error('Failed to read DOCX: ' + (e.message || e));
}
}
const raw = fs.readFileSync(inputPath, 'utf-8');
if (inputExt === 'html' || inputExt === 'htm') {
return { html: raw, text: raw.replace(/<[^>]*>/g, '') };
}
// txt, md, rtf, csv, etc — treat as plain text
const escaped = raw.replace(/</g, '<').replace(/>/g, '>');
return { html: `<pre>${escaped}</pre>`, text: raw };
}announce function · javascript · L11-L18 (8 LOC)src/js/accessibility.js
function announce(message) {
if (!announcer) return;
announcer.textContent = '';
// Force re-announcement by toggling content
requestAnimationFrame(() => {
announcer.textContent = message;
});
}Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
announceStatus function · javascript · L21-L27 (7 LOC)src/js/accessibility.js
function announceStatus(message) {
if (!status) return;
status.textContent = '';
requestAnimationFrame(() => {
status.textContent = message;
});
}setStatus function · javascript · L30-L34 (5 LOC)src/js/accessibility.js
function setStatus(message) {
const statusEl = document.getElementById('status-text');
if (statusEl) statusEl.textContent = message;
announceStatus(message);
}formatTime function · javascript · L37-L43 (7 LOC)src/js/accessibility.js
function formatTime(seconds) {
if (isNaN(seconds) || seconds < 0) return '0 seconds';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
if (mins === 0) return `${secs} second${secs !== 1 ? 's' : ''}`;
return `${mins} minute${mins !== 1 ? 's' : ''} ${secs} second${secs !== 1 ? 's' : ''}`;
}formatTimeDisplay function · javascript · L46-L51 (6 LOC)src/js/accessibility.js
function formatTimeDisplay(seconds) {
if (isNaN(seconds) || seconds < 0) return '00:00';
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}trapFocus function · javascript · L54-L79 (26 LOC)src/js/accessibility.js
function trapFocus(element) {
const focusableSelectors = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
const focusableElements = element.querySelectorAll(focusableSelectors);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
function handleTab(e) {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}
element.addEventListener('keydown', handleTab);
if (firstFocusable) firstFocusable.focus();
return () => element.removeEventListener('keydown', handleTab);
}handleTab function · javascript · L60-L73 (14 LOC)src/js/accessibility.js
function handleTab(e) {
if (e.key !== 'Tab') return;
if (e.shiftKey) {
if (document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
}
} else {
if (document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
}showModal function · javascript · L82-L89 (8 LOC)src/js/accessibility.js
function showModal(modalEl) {
modalEl.classList.remove('hidden');
modalEl.setAttribute('aria-hidden', 'false');
const cleanup = trapFocus(modalEl);
modalEl._focusCleanup = cleanup;
modalEl._previousFocus = document.activeElement;
announce('Dialog opened: ' + (modalEl.querySelector('h2')?.textContent || 'Dialog'));
}hideModal function · javascript · L92-L98 (7 LOC)src/js/accessibility.js
function hideModal(modalEl) {
modalEl.classList.add('hidden');
modalEl.setAttribute('aria-hidden', 'true');
if (modalEl._focusCleanup) modalEl._focusCleanup();
if (modalEl._previousFocus) modalEl._previousFocus.focus();
announce('Dialog closed');
}Repobility · MCP-ready · https://repobility.com
setupToolbarNavigation function · javascript · L101-L134 (34 LOC)src/js/accessibility.js
function setupToolbarNavigation(toolbar) {
const buttons = toolbar.querySelectorAll('button');
let currentIndex = 0;
toolbar.addEventListener('keydown', (e) => {
let newIndex = currentIndex;
switch (e.key) {
case 'ArrowRight':
case 'ArrowDown':
e.preventDefault();
newIndex = (currentIndex + 1) % buttons.length;
break;
case 'ArrowLeft':
case 'ArrowUp':
e.preventDefault();
newIndex = (currentIndex - 1 + buttons.length) % buttons.length;
break;
case 'Home':
e.preventDefault();
newIndex = 0;
break;
case 'End':
e.preventDefault();
newIndex = buttons.length - 1;
break;
default:
return;
}
buttons[currentIndex].setAttribute('tabindex', '-1');
buttons[newIndex].setAttribute('tabindex', '0');
buttons[newIndex].focus();
currentIndex = newIndex;
});
}setupListboxNavigation function · javascript · L137-L181 (45 LOC)src/js/accessibility.js
function setupListboxNavigation(listbox, onSelect) {
listbox.addEventListener('keydown', (e) => {
const items = listbox.querySelectorAll('[role="option"]');
if (items.length === 0) return;
const currentItem = listbox.querySelector('[aria-selected="true"]') || items[0];
let currentIdx = Array.from(items).indexOf(currentItem);
let newIdx = currentIdx;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
newIdx = Math.min(currentIdx + 1, items.length - 1);
break;
case 'ArrowUp':
e.preventDefault();
newIdx = Math.max(currentIdx - 1, 0);
break;
case 'Home':
e.preventDefault();
newIdx = 0;
break;
case 'End':
e.preventDefault();
newIdx = items.length - 1;
break;
case 'Enter':
case ' ':
e.preventDefault();
if (onSelect) onSelect(items[currentIdx]);
returinitModalEscapeHandler function · javascript · L184-L191 (8 LOC)src/js/accessibility.js
function initModalEscapeHandler() {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const openModals = document.querySelectorAll('.modal:not(.hidden)');
openModals.forEach(modal => hideModal(modal));
}
});
}searchSounds function · javascript · L39-L85 (47 LOC)src/js/ai-assistant.js
function searchSounds(query, type = 'sfx') {
if (!query) {
return 'Please specify what sound you want to find. For example: "search for rain sounds" or "find background jazz music"';
}
const results = [];
if (type === 'music' || type === 'background') {
results.push({
source: 'Pixabay Music',
url: SOUND_SOURCES.pixabay.searchUrl(query),
});
results.push({
source: 'Mixkit Music',
url: SOUND_SOURCES.mixkit.musicUrl(query),
});
} else {
results.push({
source: 'Pixabay Sound Effects',
url: SOUND_SOURCES.pixabay.sfxUrl(query),
});
results.push({
source: 'Mixkit Sound Effects',
url: SOUND_SOURCES.mixkit.searchUrl(query),
});
}
results.push({
source: 'Freesound',
url: SOUND_SOURCES.freesound.searchUrl(query),
});
results.push({
source: 'ZapSplat',
url: SOUND_SOURCES.zapsplat.searchUrl(query),
});
// OpenautoColorCorrect function · javascript · L92-L98 (7 LOC)src/js/ai-assistant.js
function autoColorCorrect() {
// Intelligent auto-correction based on common issues
Effects.setFilter('brightness', 105);
Effects.setFilter('contrast', 115);
Effects.setFilter('saturation', 110);
return 'Applied auto color correction: brightness 105%, contrast 115%, saturation 110%. This brightens dark footage and adds vibrance.';
}autoEnhancePhoto function · javascript · L101-L107 (7 LOC)src/js/ai-assistant.js
function autoEnhancePhoto() {
PhotoEditor.setAdjustment('brightness', 10);
PhotoEditor.setAdjustment('contrast', 15);
PhotoEditor.setAdjustment('saturation', 10);
PhotoEditor.setAdjustment('sharpness', 20);
return 'Applied auto enhancement: +10 brightness, +15 contrast, +10 saturation, +20 sharpness.';
}createMontage function · javascript · L110-L124 (15 LOC)src/js/ai-assistant.js
function createMontage(clipDuration = 3) {
const clips = Timeline.getClips().filter(c => c.type === 'video');
if (clips.length === 0) {
return 'No video clips on the timeline to create a montage from. Import some videos first.';
}
// Trim each clip to the specified duration and space them evenly
clips.forEach((clip, i) => {
Timeline.updateClipProperty(clip.id, 'duration', clipDuration);
Timeline.updateClipProperty(clip.id, 'startTime', i * clipDuration);
});
const totalDuration = clips.length * clipDuration;
return `Created montage: ${clips.length} clips, each ${clipDuration} seconds long. Total duration: ${totalDuration} seconds.`;
}speedRamp function · javascript · L127-L160 (34 LOC)src/js/ai-assistant.js
function speedRamp(style = 'dramatic') {
const clip = Timeline.getSelectedClip();
if (!clip) return 'No clip selected. Select a clip first.';
// Split clip into segments with varying speeds
const duration = clip.duration;
if (duration < 4) return 'Clip is too short for speed ramping. Need at least 4 seconds.';
const thirdDuration = duration / 3;
switch (style) {
case 'dramatic':
// Slow-normal-slow
Timeline.splitClip(clip.id, clip.startTime + thirdDuration);
const clips2 = Timeline.getClips();
const secondClip = clips2[clips2.length - 1];
Timeline.splitClip(secondClip.id, secondClip.startTime + thirdDuration);
const clips3 = Timeline.getClips();
// Set speeds
Timeline.updateClipProperty(clip.id, 'speed', 0.5);
Timeline.updateClipProperty(clips3[clips3.length - 2].id, 'speed', 1);
Timeline.updateClipProperty(clips3[clips3.length - 1].id, 'speed', 0.5);
return 'If a scraper extracted this row, it came from Repobility (https://repobility.com)
autoTrimSilence function · javascript · L163-L170 (8 LOC)src/js/ai-assistant.js
function autoTrimSilence() {
const clip = Timeline.getSelectedClip();
if (!clip) return 'No clip selected.';
// Trim 0.5s from start and end (common dead space)
Timeline.trimClip(clip.id, 0.5, 0.5);
return `Trimmed 0.5 seconds from start and end of "${clip.name}" to remove dead space.`;
}suggestEdits function · javascript · L173-L222 (50 LOC)src/js/ai-assistant.js
function suggestEdits() {
const clips = Timeline.getClips();
if (clips.length === 0) {
return 'No clips on the timeline. Import some media first, then I can suggest edits.';
}
const suggestions = [];
// Check total duration
const totalDuration = Timeline.getTotalDuration();
if (totalDuration > 300) {
suggestions.push('Your video is over 5 minutes. Consider trimming clips or increasing playback speed for better engagement.');
}
if (totalDuration < 10) {
suggestions.push('Your video is very short. Consider adding more clips or slowing down the playback speed.');
}
// Check for gaps
const videoClips = clips.filter(c => c.type === 'video').sort((a, b) => a.startTime - b.startTime);
for (let i = 1; i < videoClips.length; i++) {
const gap = videoClips[i].startTime - (videoClips[i-1].startTime + videoClips[i-1].duration);
if (gap > 1) {
suggestions.push(`There's a ${gap.toFixed(1)} second gap betweecloseGaps function · javascript · L225-L244 (20 LOC)src/js/ai-assistant.js
function closeGaps() {
const clips = Timeline.getClips();
const types = ['video', 'audio', 'text'];
let gapsClosed = 0;
types.forEach(type => {
const trackClips = clips.filter(c => c.type === type).sort((a, b) => a.startTime - b.startTime);
let currentEnd = 0;
trackClips.forEach(clip => {
if (clip.startTime > currentEnd) {
gapsClosed++;
Timeline.updateClipProperty(clip.id, 'startTime', currentEnd);
}
currentEnd = clip.startTime + clip.duration;
});
});
if (gapsClosed === 0) return 'No gaps found between clips.';
return `Closed ${gapsClosed} gap${gapsClosed > 1 ? 's' : ''} between clips. All clips are now seamlessly connected.`;
}addFadeEffect function · javascript · L247-L261 (15 LOC)src/js/ai-assistant.js
function addFadeEffect(type = 'both') {
const clip = Timeline.getSelectedClip();
if (!clip) return 'No clip selected.';
// Store fade info on the clip for export
if (type === 'in' || type === 'both') {
clip.fadeIn = true;
}
if (type === 'out' || type === 'both') {
clip.fadeOut = true;
}
const fadeDesc = type === 'both' ? 'fade in and fade out' : `fade ${type}`;
return `Added ${fadeDesc} effect to "${clip.name}". This will be applied during export.`;
}processAICommand function · javascript · L267-L371 (105 LOC)src/js/ai-assistant.js
function processAICommand(text) {
const lower = text.toLowerCase().trim();
// SEARCH FOR SOUNDS
const searchSFXMatch = lower.match(/(?:search|find|look for|get)\s+(?:a\s+)?(?:sound\s+(?:effect|fx)|sfx|sound)\s+(?:of\s+|for\s+)?(.+)/);
const searchMusicMatch = lower.match(/(?:search|find|look for|get)\s+(?:a\s+)?(?:background\s+)?(?:music|track|song|soundtrack)\s+(?:of\s+|for\s+|about\s+)?(.+)/);
const searchAnyMatch = lower.match(/(?:search|find|look for)\s+(.+?)(?:\s+(?:sound|music|track|audio))?$/);
if (searchSFXMatch) {
const query = searchSFXMatch[1];
const results = searchSounds(query, 'sfx');
let msg = `Searching for "${query}" sound effects. I've opened <strong>${results[0].source}</strong> in your browser.<br><br>Other sources to try:`;
msg += '<ul>' + results.slice(1).map(r => `<li><strong>${r.source}</strong></li>`).join('') + '</ul>';
msg += 'Download the sound file, then use <strong>Import Audio</strong> to add it toswitchSection function · javascript · L18-L54 (37 LOC)src/js/app.js
function switchSection(sectionId) {
// Hide all sections
document.querySelectorAll('.app-section').forEach(s => {
s.style.display = 'none';
});
// Deactivate all nav buttons
document.querySelectorAll('.nav-button').forEach(b => {
b.classList.remove('active');
b.setAttribute('aria-pressed', 'false');
});
// Show target section
const section = document.getElementById(sectionId);
if (section) {
section.style.display = 'flex';
}
// Activate nav button
for (const [btnId, secId] of Object.entries(navButtons)) {
if (secId === sectionId) {
const btn = document.getElementById(btnId);
if (btn) {
btn.classList.add('active');
btn.setAttribute('aria-pressed', 'true');
}
break;
}
}
const sectionNames = {
'section-video-editor': 'Video Editor',
'section-photo-editor': 'Photo Editor',
'section-file-converter': 'File Converter',
'sectinitNavigation function · javascript · L56-L63 (8 LOC)src/js/app.js
function initNavigation() {
for (const [btnId, sectionId] of Object.entries(navButtons)) {
const btn = document.getElementById(btnId);
if (btn) {
btn.addEventListener('click', () => switchSection(sectionId));
}
}
}addMediaToLibrary function · javascript · L80-L147 (68 LOC)src/js/app.js
function addMediaToLibrary(filePaths) {
for (const fp of filePaths) {
try {
const fileName = fp.split(/[\\/]/).pop();
const ext = fileName.split('.').pop().toLowerCase();
const isVideo = ['mp4', 'avi', 'mkv', 'mov', 'wmv', 'flv', 'webm', 'mpeg', 'mpg', 'm4v'].includes(ext);
const isAudio = ['mp3', 'wav', 'ogg', 'flac', 'aac', 'wma', 'm4a'].includes(ext);
const isImage = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff', 'svg'].includes(ext);
let type = 'video';
if (isAudio) type = 'audio';
else if (isImage) type = 'image';
// Add to media library immediately with placeholder duration
const libEntry = { path: fp, name: fileName, type, duration: isImage ? 5 : 10 };
mediaLibrary.push(libEntry);
renderMediaLibrary();
const trackType = isAudio ? 'audio' : 'video';
if (isVideo || isAudio) {
// Get real duration from a temporary media element before placiOpen data scored by Repobility · https://repobility.com
showVideoPlayer function · javascript · L150-L155 (6 LOC)src/js/app.js
function showVideoPlayer() {
const wrapper = document.getElementById('video-player-wrapper');
const noVideoMsg = document.getElementById('no-video-message');
if (wrapper) wrapper.classList.remove('hidden');
if (noVideoMsg) noVideoMsg.style.display = 'none';
}showPlacementDialog function · javascript · L158-L251 (94 LOC)src/js/app.js
function showPlacementDialog() {
console.log('showPlacementDialog called, pending:', pendingFiles.length);
if (pendingFiles.length === 0) return;
currentPendingFile = pendingFiles[0];
const dialog = document.getElementById('placement-dialog');
const nameEl = document.getElementById('placement-file-name');
const infoEl = document.getElementById('placement-file-info');
if (!dialog) {
console.error('Placement dialog element not found!');
// Fallback: just add to end of track
const file = pendingFiles.shift();
if (file) {
Timeline.addClip({
name: file.fileName,
type: file.trackType,
filePath: file.filePath,
duration: file.duration,
});
Accessibility.announce(file.fileName + ' added to timeline.');
}
currentPendingFile = null;
return;
}
try {
// Update dialog text
const playheadTime = Player.getCurrentTime ? Player.getCurrentTime() : placePendingFile function · javascript · L255-L287 (33 LOC)src/js/app.js
function placePendingFile(startTime) {
if (!currentPendingFile || isPlacing) return;
isPlacing = true;
const file = currentPendingFile;
// Remove from queue FIRST to prevent double-add
pendingFiles.shift();
currentPendingFile = null;
Timeline.addClip({
name: file.fileName,
type: file.trackType,
filePath: file.filePath,
duration: file.duration,
startTime,
});
const placedAt = Accessibility.formatTime(startTime);
Accessibility.announce(
`${file.fileName} placed on ${file.trackType} track at ${placedAt}. Duration: ${Accessibility.formatTime(file.duration)}.`
);
// Close dialog
Accessibility.hideModal(document.getElementById('placement-dialog'));
isPlacing = false;
// Show next if there are more
if (pendingFiles.length > 0) {
setTimeout(showPlacementDialog, 300);
}
}initPlacementDialog function · javascript · L290-L328 (39 LOC)src/js/app.js
function initPlacementDialog() {
document.getElementById('placement-start-track')?.addEventListener('click', () => {
placePendingFile(0);
});
document.getElementById('placement-playhead')?.addEventListener('click', () => {
placePendingFile(Player.getCurrentTime());
});
document.getElementById('placement-end-track')?.addEventListener('click', () => {
if (!currentPendingFile) return;
placePendingFile(Timeline.getTrackEndTime(currentPendingFile.trackType));
});
document.getElementById('placement-after-selected')?.addEventListener('click', () => {
const clip = Timeline.getSelectedClip();
if (clip) {
placePendingFile(clip.startTime + clip.duration);
}
});
document.getElementById('placement-before-selected')?.addEventListener('click', () => {
const clip = Timeline.getSelectedClip();
if (clip) {
placePendingFile(clip.startTime);
}
});
document.getElementById('placemenrenderMediaLibrary function · javascript · L330-L370 (41 LOC)src/js/app.js
function renderMediaLibrary() {
const list = document.getElementById('media-list');
const emptyMsg = document.getElementById('media-empty-message');
if (!list) return;
// Clear
list.innerHTML = '';
if (mediaLibrary.length === 0) {
if (emptyMsg) {
list.appendChild(emptyMsg);
emptyMsg.style.display = 'block';
}
return;
}
mediaLibrary.forEach((item, idx) => {
const el = document.createElement('div');
el.className = 'media-item';
el.setAttribute('role', 'listitem');
el.setAttribute('tabindex', '0');
el.setAttribute('aria-label', `${item.name}, ${item.type}, ${item.duration > 0 ? 'duration ' + Accessibility.formatTime(item.duration) : 'image'}`);
el.setAttribute('aria-selected', idx === selectedMediaIndex ? 'true' : 'false');
const typeIcon = item.type === 'video' ? '[V]' : item.type === 'audio' ? '[A]' : '[I]';
el.innerHTML = `
<span class="media-name">${typeIcon}selectMedia function · javascript · L372-L392 (21 LOC)src/js/app.js
function selectMedia(index) {
selectedMediaIndex = index;
const addBtn = document.getElementById('btn-add-to-timeline');
if (addBtn) addBtn.disabled = false;
// Update selection visuals
document.querySelectorAll('.media-item').forEach((el, i) => {
el.setAttribute('aria-selected', i === index ? 'true' : 'false');
el.classList.toggle('selected', i === index);
});
const item = mediaLibrary[index];
if (item) {
// Load preview
if (item.type === 'video') {
Player.loadVideo(item.path);
showVideoPlayer();
}
Accessibility.announce(`Selected: ${item.name}`);
}
}addSelectedToTimeline function · javascript · L394-L406 (13 LOC)src/js/app.js
function addSelectedToTimeline() {
if (selectedMediaIndex < 0 || selectedMediaIndex >= mediaLibrary.length) {
Accessibility.announce('No media selected. Select a file from the media library first.');
return;
}
const item = mediaLibrary[selectedMediaIndex];
Timeline.addClip({
name: item.name,
type: item.type === 'image' ? 'video' : item.type,
filePath: item.path,
duration: item.duration || 5,
});
}initVideoToolbar function · javascript · L411-L647 (237 LOC)src/js/app.js
function initVideoToolbar() {
// Import media
document.getElementById('btn-import-media')?.addEventListener('click', async () => {
if (window.api) {
const result = await window.api.importMedia();
if (!result.canceled && result.filePaths.length > 0) {
addMediaToLibrary(result.filePaths);
}
}
});
// Import audio
document.getElementById('btn-import-audio')?.addEventListener('click', async () => {
if (window.api) {
const result = await window.api.importAudio();
if (!result.canceled && result.filePaths.length > 0) {
addMediaToLibrary(result.filePaths);
}
}
});
// New project
document.getElementById('btn-new-project')?.addEventListener('click', () => {
Timeline.clearAll();
mediaLibrary = [];
selectedMediaIndex = -1;
renderMediaLibrary();
Accessibility.announce('New project created');
Accessibility.setStatus('New project');
});Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
initPhotoToolbar function · javascript · L652-L767 (116 LOC)src/js/app.js
function initPhotoToolbar() {
document.getElementById('btn-photo-open')?.addEventListener('click', async () => {
if (!window.api) return;
const result = await window.api.showOpenDialog({
title: 'Open Image',
filters: [
{ name: 'Image Files', extensions: ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'tiff'] },
],
properties: ['openFile'],
});
if (!result.canceled && result.filePaths.length > 0) {
PhotoEditor.loadImage(result.filePaths[0]);
}
});
document.getElementById('btn-photo-save')?.addEventListener('click', async () => {
if (!PhotoEditor.hasImage) {
Accessibility.announce('No image to save');
return;
}
Accessibility.announce('Use Save As to choose format and location');
});
document.getElementById('btn-photo-save-as')?.addEventListener('click', async () => {
if (!PhotoEditor.hasImage || !window.api) return;
const result = await windowinitExportDialog function · javascript · L772-L830 (59 LOC)src/js/app.js
function initExportDialog() {
document.getElementById('btn-export-start')?.addEventListener('click', async () => {
const format = document.getElementById('export-format')?.value || 'mp4';
const resolution = document.getElementById('export-resolution')?.value || '1920:1080';
const fps = document.getElementById('export-fps')?.value || '30';
const quality = document.getElementById('export-quality')?.value || 'medium';
const clips = Timeline.getVideoClips();
if (clips.length === 0) {
Accessibility.announce('No clips to export. Add media to the timeline first.');
return;
}
if (!window.api) {
Accessibility.announce('Export requires the desktop application');
return;
}
const result = await window.api.showSaveDialog({
title: 'Export Video',
defaultPath: `output.${format}`,
filters: [{ name: `${format.toUpperCase()} Video`, extensions: [format] }],
});
if (reinitTextDialog function · javascript · L835-L867 (33 LOC)src/js/app.js
function initTextDialog() {
document.getElementById('btn-text-add')?.addEventListener('click', () => {
const text = document.getElementById('text-content-input')?.value || 'Text';
const fontSize = parseInt(document.getElementById('text-font-size-input')?.value) || 48;
const color = document.getElementById('text-color-input')?.value || '#ffffff';
const position = document.getElementById('text-position-select')?.value || 'center';
const duration = parseFloat(document.getElementById('text-duration-input')?.value) || 5;
// Place text at playhead position, or at end of text track
const playheadTime = Player.getCurrentTime();
const trackEnd = Timeline.getTrackEndTime('text');
const placeAt = (playheadTime > 0) ? playheadTime : trackEnd;
Timeline.addClip({
name: 'Text: ' + text.substring(0, 20),
type: 'text',
text,
fontSize,
textColor: color,
textPosition: position,
durainitSettings function · javascript · L872-L959 (88 LOC)src/js/app.js
function initSettings() {
// Theme / Contrast / Font size
document.getElementById('btn-apply-theme')?.addEventListener('click', () => {
const theme = document.getElementById('theme-mode-select')?.value || 'dark';
const contrast = document.getElementById('contrast-level-select')?.value || 'normal';
const fontSize = document.getElementById('font-size-select')?.value || 'normal';
// Apply theme
document.body.classList.remove('light-mode');
if (theme === 'light') document.body.classList.add('light-mode');
// Apply contrast
document.body.classList.remove('high-contrast', 'very-high-contrast');
if (contrast === 'high') document.body.classList.add('high-contrast');
else if (contrast === 'very-high') document.body.classList.add('very-high-contrast');
// Apply font size
document.body.classList.remove('font-small', 'font-normal', 'font-large', 'font-very-large');
document.body.classList.add(`font-${fontSizinitKeyboardShortcuts function · javascript · L964-L1058 (95 LOC)src/js/app.js
function initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Don't capture when typing in an input
const isTyping = ['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement?.tagName);
// F1 - shortcuts dialog (always works)
if (e.key === 'F1') {
e.preventDefault();
const dialog = document.getElementById('shortcuts-dialog');
if (dialog) Accessibility.showModal(dialog);
return;
}
// Ctrl combinations (always work)
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case 'z':
e.preventDefault();
Timeline.undo();
return;
case 'y':
e.preventDefault();
Timeline.redo();
return;
case 'i':
e.preventDefault();
if (e.shiftKey) {
document.getElementById('btn-import-audio')?.click();
} else {
document.getEleminitDialogs function · javascript · L1063-L1071 (9 LOC)src/js/app.js
function initDialogs() {
document.getElementById('btn-keyboard-shortcuts')?.addEventListener('click', () => {
Accessibility.showModal(document.getElementById('shortcuts-dialog'));
});
document.getElementById('btn-shortcuts-close')?.addEventListener('click', () => {
Accessibility.hideModal(document.getElementById('shortcuts-dialog'));
});
}initIPC function · javascript · L1076-L1106 (31 LOC)src/js/app.js
function initIPC() {
if (!window.api) return;
window.api.onFilesImported((files) => addMediaToLibrary(files));
window.api.onAudioImported((files) => addMediaToLibrary(files));
window.api.onMenuAction((action) => {
switch (action) {
case 'new-project': document.getElementById('btn-new-project')?.click(); break;
case 'save-project': document.getElementById('btn-save-project')?.click(); break;
case 'export': document.getElementById('btn-export-video')?.click(); break;
case 'undo': Timeline.undo(); break;
case 'redo': Timeline.redo(); break;
case 'split': Timeline.splitAtPlayhead(); break;
case 'delete-clip':
const c = Timeline.getSelectedClip();
if (c) Timeline.removeClip(c.id);
break;
case 'duplicate': Timeline.duplicateClip(); break;
case 'zoom-in': Timeline.zoomIn(); break;
case 'zoom-out': Timeline.zoomOut(); break;
case 'toggle-chatbot': documloadProjectList function · javascript · L1111-L1197 (87 LOC)src/js/app.js
async function loadProjectList() {
const listEl = document.getElementById('projects-list');
const emptyMsg = document.getElementById('projects-empty-message');
if (!listEl || !window.api) return;
try {
const projects = await window.api.listProjects();
listEl.innerHTML = '';
if (projects.length === 0) {
if (emptyMsg) {
listEl.appendChild(emptyMsg);
emptyMsg.style.display = 'block';
}
return;
}
projects.forEach(proj => {
const item = document.createElement('div');
item.className = 'project-item';
item.setAttribute('role', 'listitem');
item.setAttribute('tabindex', '0');
item.setAttribute('aria-label', `Project: ${proj.name}, saved ${new Date(proj.date).toLocaleDateString()}`);
const dateStr = new Date(proj.date).toLocaleDateString(undefined, {
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit',Repobility · MCP-ready · https://repobility.com
init function · javascript · L1202-L1233 (32 LOC)src/js/app.js
function init() {
initNavigation();
initVideoToolbar();
initPhotoToolbar();
initExportDialog();
initTextDialog();
initSettings();
initKeyboardShortcuts();
initDialogs();
initPlacementDialog();
initIPC();
// Init sub-modules
Player.init();
Effects.init();
PhotoEditor.init();
Chatbot.init();
Converter.init();
Timeline.initTimelineClick();
Timeline.renderAllTracks();
// Setup toolbar navigation for screen readers
document.querySelectorAll('[role="toolbar"]').forEach(toolbar => {
Accessibility.setupToolbarNavigation(toolbar);
});
initClipList();
loadProjectList();
Accessibility.setStatus('Ready. Use the sidebar to navigate between sections.');
Accessibility.announce('Accessible Studio loaded. Use the sidebar buttons to switch between Video Editor, Photo Editor, File Converter, User Guide, and Settings.');
}renderClipList function · javascript · L1241-L1250 (10 LOC)src/js/app.js
function renderClipList() {
const allClips = Timeline.getClips();
const videoClips = allClips.filter(c => c.type === 'video' || c.type === 'image').sort((a, b) => a.startTime - b.startTime);
const audioClips = allClips.filter(c => c.type === 'audio').sort((a, b) => a.startTime - b.startTime);
const selectedClip = Timeline.getSelectedClip();
renderClipSelect('video-clip-select', videoClips, selectedClip, 'No video clips yet');
renderClipSelect('audio-clip-select', audioClips, selectedClip, 'No audio clips yet');
updateClipActionButtons();
}renderClipSelect function · javascript · L1252-L1285 (34 LOC)src/js/app.js
function renderClipSelect(selectId, clips, selectedClip, emptyMsg) {
const select = document.getElementById(selectId);
if (!select) return;
const prevValue = select.value;
select.innerHTML = '';
if (clips.length === 0) {
const opt = document.createElement('option');
opt.value = '';
opt.disabled = true;
opt.selected = true;
opt.textContent = emptyMsg;
select.appendChild(opt);
return;
}
clips.forEach((clip, i) => {
const opt = document.createElement('option');
opt.value = clip.id;
const startStr = Accessibility.formatTimeDisplay(clip.startTime);
const endTime = clip.startTime + clip.duration;
const endStr = Accessibility.formatTimeDisplay(endTime);
opt.textContent = `Clip ${i + 1}: ${clip.name} — ${startStr} to ${endStr}`;
if (selectedClip && selectedClip.id === clip.id) {
opt.selected = true;
}
select.appendChild(opt);
});
// Restore previous spage 1 / 4next ›