Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
4f0c9ca
Replace fixed width with dynamic cardWidth
ShaneIsrael Jan 4, 2026
2a16df1
Add caching headers for static game assets and video thumbnails
dammitjeff Jan 11, 2026
c4c1b40
removed redundant text in the "link to game" text box in edit mode
dammitjeff Jan 11, 2026
7fe03ae
added basic fuzzy autodetection for games
dammitjeff Jan 14, 2026
f4c8dbf
added manual scan for games in settings
dammitjeff Jan 14, 2026
b7a6da5
checks if existing game is in database, preventing duplicates
dammitjeff Jan 14, 2026
d825ea4
changing some text
dammitjeff Jan 14, 2026
7322b92
made Games tab visibility optional
dammitjeff Jan 15, 2026
b1a4ba5
Added options to disable My Videos and Public Videos
dammitjeff Jan 15, 2026
9729699
fixed deleting game bug
dammitjeff Jan 15, 2026
682dfb0
Merge pull request #379 from dammitjeff/clean-caching
ShaneIsrael Jan 15, 2026
15d6e21
Add game scan status indicator in sidebar
dammitjeff Jan 17, 2026
3071fba
Merge pull request #381 from dammitjeff/clean-caching
ShaneIsrael Jan 17, 2026
81ae937
Merge pull request #383 from ShaneIsrael/main
ShaneIsrael Jan 17, 2026
987cbf9
Basic game detection now scans folder path, added Clear Database butt…
dammitjeff Jan 17, 2026
b731edc
Initial plan
Copilot Jan 17, 2026
a04cef2
Update nginx configs to use IPv4 localhost (127.0.0.1) instead of loc…
Copilot Jan 17, 2026
9dcc2ad
Merge pull request #385 from ShaneIsrael/copilot/update-nginx-to-use-…
ShaneIsrael Jan 17, 2026
1a7615a
Merge pull request #384 from dammitjeff/clean-caching
ShaneIsrael Jan 17, 2026
d69d887
Initial plan
Copilot Jan 17, 2026
585a151
Add validate_video_file function to detect corrupt videos before tran…
Copilot Jan 17, 2026
368c24c
Address code review feedback: move constants, fix error handling
Copilot Jan 17, 2026
3e3267f
Clean up extra blank line in util.py
Copilot Jan 17, 2026
1cf3611
Merge pull request #386 from ShaneIsrael/copilot/fix-transcoding-corr…
ShaneIsrael Jan 17, 2026
91de6b6
Initial plan
Copilot Jan 18, 2026
f3aa68e
Fix transcoding corruption handling: distinguish corruption vs encode…
Copilot Jan 18, 2026
0bf4f33
Address code review: improve video stream handling and use AV1 false …
Copilot Jan 18, 2026
b6fecb6
Address code review: use constants for AV1 codecs and consistent case…
Copilot Jan 18, 2026
9419a78
Improve code comments for clarity and maintainability
Copilot Jan 18, 2026
7829aef
Merge pull request #387 from ShaneIsrael/copilot/investigate-file-cor…
ShaneIsrael Jan 18, 2026
3a20b8f
Initial plan
Copilot Jan 18, 2026
47332d4
Fix AttributeError in delete_game when VideoGameLink has orphaned vid…
Copilot Jan 18, 2026
02bc7db
Merge pull request #388 from ShaneIsrael/copilot/fix-delete-api-error
ShaneIsrael Jan 18, 2026
8aec029
Initial plan
Copilot Jan 18, 2026
b62f480
Fix video modal showing old thumbnail and video width stretching past…
Copilot Jan 18, 2026
df3828f
Merge pull request #389 from ShaneIsrael/copilot/fix-video-modal-data…
ShaneIsrael Jan 18, 2026
8dc692e
Initial plan
Copilot Jan 18, 2026
f778434
Fix nginx config to properly serve static files from /_content/ path
Copilot Jan 18, 2026
628c905
Use alias consistently in dev template for /_content/video/ location
Copilot Jan 18, 2026
df0cdca
Merge pull request #390 from ShaneIsrael/copilot/fix-nginx-404-error-…
ShaneIsrael Jan 18, 2026
b9a4ca0
Add key prop to VideoJSPlayer component so that it correctly re-rende…
ShaneIsrael Jan 18, 2026
40d36e1
Add logging to GameScanStatus for better debugging and update success…
dammitjeff Jan 18, 2026
c99c406
Update background color of GameDetectionCard for improved visibility
dammitjeff Jan 18, 2026
6289d13
Implement search functionality in GameVideos component
dammitjeff Jan 18, 2026
00ede9e
Fix shebang and update user commands in entrypoint.sh
ShaneIsrael Jan 19, 2026
642aa8e
Fix chown command syntax for VIDEO_DIRECTORY
ShaneIsrael Jan 19, 2026
176005a
Enhance Nginx config for better performance
ShaneIsrael Jan 19, 2026
c080063
Refactor entrypoint.sh for clarity and updates
ShaneIsrael Jan 19, 2026
90249c6
Add Gunicorn configuration for Fireshare server
ShaneIsrael Jan 19, 2026
a4753a5
Enhance SQLite configuration for concurrency
ShaneIsrael Jan 19, 2026
10ff2ce
Update entrypoint.sh to start nginx as ROOT
ShaneIsrael Jan 19, 2026
f084e26
Add folder suggestion feature, removed debug "reset database" button
dammitjeff Jan 19, 2026
2308c12
Enhance entrypoint.sh with logging and error handling
ShaneIsrael Jan 19, 2026
e4ad8c4
Fix formatting issue in gunicorn configuration
ShaneIsrael Jan 19, 2026
f40323a
Fix error messages and update gunicorn startup comment
ShaneIsrael Jan 19, 2026
4289b88
Fix chown command syntax in entrypoint.sh
ShaneIsrael Jan 19, 2026
05ec86d
Simplify entrypoint.sh and adjust user handling
ShaneIsrael Jan 19, 2026
ead0da9
fixed logic break when fetching folder groups
dammitjeff Jan 19, 2026
6869320
Fix chown command and update gunicorn port
ShaneIsrael Jan 19, 2026
3c6284a
Change fireshare port mapping from 8080:80 to 8080:5000
ShaneIsrael Jan 19, 2026
9809661
Change port mapping for fireshare service
ShaneIsrael Jan 19, 2026
3e73df5
Update entrypoint.sh for user permissions and logging
ShaneIsrael Jan 19, 2026
e3957d0
Replace runuser with su for appuser commands
ShaneIsrael Jan 19, 2026
9e530f9
Refactor entrypoint.sh for debugging and user management
ShaneIsrael Jan 19, 2026
8bf8422
Refactor entrypoint.sh for debugging and clarity
ShaneIsrael Jan 19, 2026
f2e023d
Fix export PATH formatting in entrypoint.sh
ShaneIsrael Jan 19, 2026
f8fd034
Fix syntax for app creation in entrypoint.sh
ShaneIsrael Jan 19, 2026
a1b9e8a
Enhance entrypoint.sh for user management and nginx
ShaneIsrael Jan 19, 2026
ddf76cb
Fix lockfile and sqlite file removal commands
ShaneIsrael Jan 19, 2026
bcb6e0b
Update entrypoint.sh
ShaneIsrael Jan 19, 2026
3774fe6
Refactor nginx configuration for clarity and performance
ShaneIsrael Jan 19, 2026
45e44b1
Fix error log path in nginx configuration
ShaneIsrael Jan 19, 2026
3927544
Simplify user-switching commands for migrations and gunicorn
ShaneIsrael Jan 19, 2026
ed056e0
Remove SQLAlchemy engine options from config
ShaneIsrael Jan 19, 2026
703a877
Remove duplicate SQLALCHEMY_TRACK_MODIFICATIONS config
ShaneIsrael Jan 19, 2026
2c2e9cd
Initial plan
Copilot Jan 19, 2026
7d89148
Fix SQLite database path for APScheduler to use absolute path
Copilot Jan 19, 2026
97446bc
Merge pull request #393 from ShaneIsrael/copilot/fix-gunicorn-worker-…
ShaneIsrael Jan 19, 2026
27714aa
Refactor entrypoint.sh for nginx and gunicorn
ShaneIsrael Jan 19, 2026
23bb807
Merge branch 'develop' into performance-improvements
ShaneIsrael Jan 19, 2026
b8bd871
Merge pull request #394 from ShaneIsrael/performance-improvements
ShaneIsrael Jan 19, 2026
7d25902
Merge pull request #392 from dammitjeff/autodetect-prod
ShaneIsrael Jan 19, 2026
08d049c
Initial plan
Copilot Jan 20, 2026
a18ab91
Remove worker_rlimit_nofile directive to fix RLIMIT_NOFILE errors on …
Copilot Jan 20, 2026
d49d1bb
Merge pull request #395 from ShaneIsrael/copilot/fix-shutdown-error
ShaneIsrael Jan 20, 2026
ba4fdd7
Initial plan
Copilot Jan 20, 2026
5834185
Fix AV1 validation to reduce false positive corruption detection
Copilot Jan 20, 2026
8a7b9e3
Merge pull request #396 from ShaneIsrael/copilot/improve-av1-file-val…
ShaneIsrael Jan 20, 2026
f4ce30e
Update logging to show count of suggestion keys
ShaneIsrael Jan 20, 2026
392358a
Initial plan
Copilot Jan 20, 2026
8fde10c
Improve SQLite performance with connection pooling and timeout settings
Copilot Jan 20, 2026
ca89ee8
Use NullPool instead of connection pooling for SQLite
Copilot Jan 20, 2026
8c04e4f
Fix false positive corruption detection for AV1 files
Copilot Jan 20, 2026
8e09944
Merge pull request #397 from ShaneIsrael/copilot/improve-sqlite-perfo…
ShaneIsrael Jan 20, 2026
19cb25e
Initial plan
Copilot Jan 20, 2026
d4c49fa
Fix AV1 transcoding errors by moving -t option to output side of ffmp…
Copilot Jan 20, 2026
359e785
Merge pull request #398 from ShaneIsrael/copilot/fix-av1-transcoding-…
ShaneIsrael Jan 20, 2026
42cffde
Remove invalid data error message from util.py
ShaneIsrael Jan 20, 2026
83ab814
Clean up comments in ffmpeg decode command
ShaneIsrael Jan 20, 2026
494b248
Initial plan
Copilot Jan 20, 2026
eed734a
Add persistent tracking for corrupt videos during transcoding
Copilot Jan 20, 2026
5610018
Refactor transcode_videos to use helper methods for corrupt handling
Copilot Jan 20, 2026
c62d925
Switch corrupt video tracking from database to file-based storage
Copilot Jan 20, 2026
54dbe14
Address code review feedback - improve performance
Copilot Jan 20, 2026
2f85590
Merge pull request #399 from ShaneIsrael/copilot/handle-corrupt-files…
ShaneIsrael Jan 20, 2026
07140b9
Add automatic organizing by games feature
dammitjeff Jan 20, 2026
9f730cd
Fixed bug where folder game detection would pick up upload folders as…
dammitjeff Jan 20, 2026
59e44d8
Merge pull request #400 from dammitjeff/changing-desc
ShaneIsrael Jan 20, 2026
7461cfd
Merge pull request #401 from dammitjeff/develop
ShaneIsrael Jan 20, 2026
7c1743a
Delete changelog from README.md
ShaneIsrael Jan 20, 2026
ddfe535
Sort games by name in the Games view and API response
dammitjeff Jan 20, 2026
3e5aaa2
Merge pull request #402 from dammitjeff/develop
ShaneIsrael Jan 20, 2026
5d2c7cc
Bump version from 1.3.3 to 1.4.0
ShaneIsrael Jan 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ The goal of Fireshare is to provide a very simple and easy way for you to share

![list-view]

<h2 align="center">Foldered Sorting</h2>
<p align="center">Fireshare will use the top most directory that your videos are in as an easy and simple way for you to organize your videos into categories of your choosing.</p>

![folders]
<h2 align="center">NEW! Automatic Organizing by Games</h2>
<p align="center">Organize your video clips by games so you know exactly where they are. Fireshare can automatically scan and organize your video clips by games, and organize them with beautiful and relevant cover art.</p>
<img width="3784" height="1798" alt="Gamestab" src="https://github.com/user-attachments/assets/8d43a29a-3e86-46ff-a598-96331a4cd2f2" />

<h2 align="center">Uploading</h2>
<p align="center">Allow your community or the public the ability to upload videos. Of course, this feature can be disabled or limited to only administrator access</p>
![Uploading Gamestab.png…]()

![uploading]

Expand Down Expand Up @@ -138,15 +138,6 @@ Connect Fireshare to a central user directory and keep user access organised.
- [Python](https://www.python.org/)
- [Material UI](https://mui.com/)

<!--- CHANGE LOG --->

# Changelog

## v1.2.13
```
Added a catch for finding corrupt or malformed files when initiating a scan
```

<!-- GETTING STARTED -->

# Installation
Expand Down
4 changes: 2 additions & 2 deletions app/client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fireshare",
"version": "1.3.3",
"version": "1.4.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.9.0",
Expand Down
44 changes: 38 additions & 6 deletions app/client/src/components/admin/CompactVideoCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import VideoService from '../../services/VideoService'
import _ from 'lodash'
import UpdateDetailsModal from '../modal/UpdateDetailsModal'
import LightTooltip from '../misc/LightTooltip'
import GameDetectionCard from '../game/GameDetectionCard'

const URL = getUrl()
const PURL = getPublicWatchUrl()
Expand All @@ -36,6 +37,8 @@ const CompactVideoCard = ({
const [privateView, setPrivateView] = React.useState(video.info?.private)

const [detailsModalOpen, setDetailsModalOpen] = React.useState(false)
const [gameSuggestion, setGameSuggestion] = React.useState(null)
const [showSuggestion, setShowSuggestion] = React.useState(true)

const previousVideoRef = React.useRef()
const previousVideo = previousVideoRef.current
Expand All @@ -51,6 +54,25 @@ const CompactVideoCard = ({
previousVideoRef.current = video
})

React.useEffect(() => {
// Fetch game suggestion when component mounts
VideoService.getGameSuggestion(video.video_id)
.then((response) => {
if (response.data) {
setGameSuggestion(response.data)
setShowSuggestion(true)
}
})
.catch(() => {
// No suggestion or error - that's fine
})
}, [video.video_id])

const handleSuggestionComplete = () => {
setShowSuggestion(false)
setGameSuggestion(null)
}

const debouncedMouseEnter = React.useRef(
_.debounce(() => {
setHover(true)
Expand Down Expand Up @@ -164,9 +186,8 @@ const CompactVideoCard = ({
variant="contained"
size="small"
sx={{
width: '100%',
width: cardWidth,
background: '#0b132b',

borderRadius: '6px',
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
Expand Down Expand Up @@ -239,6 +260,7 @@ const CompactVideoCard = ({
</ButtonGroup>
<Box
sx={{
width: cardWidth,
lineHeight: 0,
bgcolor: 'rgba(0,0,0,0)',
p: 0,
Expand Down Expand Up @@ -304,8 +326,8 @@ const CompactVideoCard = ({
width: cardWidth,
minHeight: previewVideoHeight,
border: '1px solid #3399FFAE',
borderBottomRightRadius: '6px',
borderBottomLeftRadius: '6px',
borderBottomRightRadius: (authenticated && gameSuggestion && showSuggestion && !editMode) ? 0 : '6px',
borderBottomLeftRadius: (authenticated && gameSuggestion && showSuggestion && !editMode) ? 0 : '6px',
borderTop: 'none',
background: 'repeating-linear-gradient(45deg,#606dbc,#606dbc 10px,#465298 10px,#465298 20px)',
overflow: 'hidden'
Expand All @@ -326,8 +348,8 @@ const CompactVideoCard = ({
WebkitAnimationDuration: '1.5s',
WebkitAnimationFillMode: 'both',
border: '1px solid #3399FFAE',
borderBottomRightRadius: '6px',
borderBottomLeftRadius: '6px',
borderBottomRightRadius: (authenticated && gameSuggestion && showSuggestion && !editMode) ? 0 : '6px',
borderBottomLeftRadius: (authenticated && gameSuggestion && showSuggestion && !editMode) ? 0 : '6px',
borderTop: 'none',
overflow: 'hidden'
}}
Expand Down Expand Up @@ -398,6 +420,16 @@ const CompactVideoCard = ({
</Box>
</div>
</Box>

{/* Game Detection Suggestion Card */}
{authenticated && gameSuggestion && showSuggestion && !editMode && (
<GameDetectionCard
videoId={video.video_id}
suggestion={gameSuggestion}
onComplete={handleSuggestionComplete}
cardWidth={cardWidth}
/>
)}
</Box>
</>
)
Expand Down
181 changes: 181 additions & 0 deletions app/client/src/components/game/GameDetectionCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import React, { useState } from 'react'
import { Box, IconButton, Typography, Fade } from '@mui/material'
import CheckIcon from '@mui/icons-material/Check'
import CloseIcon from '@mui/icons-material/Close'
import SportsEsportsIcon from '@mui/icons-material/SportsEsports'
import VideoService from '../../services/VideoService'
import GameService from '../../services/GameService'

/**
* GameDetectionCard - Shows automatic game detection suggestions
* Appears below video thumbnails when a game is detected from the filename
*/
export default function GameDetectionCard({ videoId, suggestion, onComplete, cardWidth }) {
const [loading, setLoading] = useState(false)
const [status, setStatus] = useState('pending') // 'pending', 'accepted', 'rejected'

const handleAccept = async (e) => {
e.stopPropagation() // Prevent triggering video card click
setLoading(true)

try {
let gameId = suggestion.game_id

// If game doesn't exist in our DB (came from SteamGridDB), create it first
if (!gameId && suggestion.steamgriddb_id) {
// Reuse the same logic as GameSearch.js
const assets = (await GameService.getGameAssets(suggestion.steamgriddb_id)).data
const gameData = {
steamgriddb_id: suggestion.steamgriddb_id,
name: suggestion.game_name,
hero_url: assets.hero_url,
logo_url: assets.logo_url,
icon_url: assets.icon_url,
}
const createdGame = (await GameService.createGame(gameData)).data
gameId = createdGame.id
}

// Link video to game using existing service
await GameService.linkVideoToGame(videoId, gameId)

// Remove the suggestion from cache
await VideoService.rejectGameSuggestion(videoId)

setStatus('accepted')
// Auto-hide after showing success
setTimeout(() => {
onComplete?.()
}, 2000)
} catch (err) {
console.error('Failed to accept game suggestion:', err)
setLoading(false)
}
}

const handleReject = async (e) => {
e.stopPropagation() // Prevent triggering video card click
setLoading(true)

try {
await VideoService.rejectGameSuggestion(videoId)
setStatus('rejected')
// Hide immediately
setTimeout(() => {
onComplete?.()
}, 300)
} catch (err) {
console.error('Failed to reject game suggestion:', err)
setLoading(false)
}
}

if (status === 'accepted') {
return (
<Fade in timeout={500}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 1,
p: 1,
width: cardWidth,
background: 'rgba(76, 175, 80, 0.2)',
borderLeft: '1px solid rgba(76, 175, 80, 0.8)',
borderRight: '1px solid rgba(76, 175, 80, 0.8)',
borderBottom: '1px solid rgba(76, 175, 80, 0.8)',
borderBottomLeftRadius: '6px',
borderBottomRightRadius: '6px',
lineHeight: 0,
}}
>
<CheckIcon sx={{ color: '#4caf50', fontSize: 20 }} />
<Typography variant="body2" sx={{ color: '#4caf50', fontWeight: 600 }}>
Linked to {suggestion.game_name}
</Typography>
</Box>
</Fade>
)
}

if (status === 'rejected') {
return null
}

return (
<Fade in timeout={500}>
<Box
onClick={(e) => e.stopPropagation()} // Prevent triggering video card click
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
p: 1,
width: cardWidth,
background: '#101c3c',
borderLeft: '1px solid #3399FFAE',
borderRight: '1px solid #3399FFAE',
borderBottom: '1px solid #3399FFAE',
borderBottomLeftRadius: '6px',
borderBottomRightRadius: '6px',
lineHeight: 0,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flex: 1, minWidth: 0 }}>
<SportsEsportsIcon sx={{ color: '#3399FF', fontSize: 20, flexShrink: 0 }} />
<Typography
variant="body2"
sx={{
color: 'rgba(255, 255, 255, 0.95)',
fontSize: '0.875rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
Detected: <strong>{suggestion.game_name}</strong>
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 0.5, flexShrink: 0 }}>
<IconButton
size="small"
onClick={handleAccept}
disabled={loading}
sx={{
color: '#4caf50',
bgcolor: 'rgba(76, 175, 80, 0.1)',
'&:hover': {
bgcolor: 'rgba(76, 175, 80, 0.2)',
transform: 'scale(1.1)',
},
transition: 'all 0.2s',
width: 28,
height: 28,
}}
>
<CheckIcon fontSize="small" />
</IconButton>
<IconButton
size="small"
onClick={handleReject}
disabled={loading}
sx={{
color: '#f44336',
bgcolor: 'rgba(244, 67, 54, 0.1)',
'&:hover': {
bgcolor: 'rgba(244, 67, 54, 0.2)',
transform: 'scale(1.1)',
},
transition: 'all 0.2s',
width: 28,
height: 28,
}}
>
<CloseIcon fontSize="small" />
</IconButton>
</Box>
</Box>
</Fade>
)
}
8 changes: 7 additions & 1 deletion app/client/src/components/modal/VideoModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ const VideoModal = ({ open, onClose, videoId, feedView, authenticated, updateCal
}
}
if (videoId) {
// Reset video state before loading new video to prevent showing old data
setVideo(null)
setTitle('')
setDescription('')
setSelectedGame(null)
fetch()
}
}, [videoId])
Expand Down Expand Up @@ -272,12 +277,13 @@ const VideoModal = ({ open, onClose, videoId, feedView, authenticated, updateCal
<Box
sx={{
width: '100%',
maxWidth: 'calc((100vh - 40px - 200px) * 16 / 9)',
maxWidth: 'min(calc((100vh - 40px - 200px) * 16 / 9), calc(100vw - 40px))',
display: 'flex',
flexDirection: 'column',
}}
>
<VideoJSPlayer
key={vid.video_id}
sources={getVideoSources(vid.video_id, vid?.info, vid.extension)}
poster={getPosterUrl()}
autoplay={autoplay}
Expand Down
Loading
Loading