diff --git a/graphql/schema/types/metadata.graphql b/graphql/schema/types/metadata.graphql index 1722f44e4..a62028c29 100644 --- a/graphql/schema/types/metadata.graphql +++ b/graphql/schema/types/metadata.graphql @@ -34,6 +34,8 @@ input ScanMetadataInput { paths: [String!] """Set name, date, details from metadata (if present)""" useFileMetadata: Boolean! + """Strip file extension from title""" + stripFileExtension: Boolean! """Generate previews during scan""" scanGeneratePreviews: Boolean! """Generate image previews during scan""" diff --git a/pkg/api/routes_scene.go b/pkg/api/routes_scene.go index 046ac09f9..e979ecf4b 100644 --- a/pkg/api/routes_scene.go +++ b/pkg/api/routes_scene.go @@ -53,7 +53,7 @@ func getSceneFileContainer(scene *models.Scene) ffmpeg.Container { container = ffmpeg.Container(scene.Format.String) } else { // container isn't in the DB // shouldn't happen, fallback to ffprobe - tmpVideoFile, err := ffmpeg.NewVideoFile(manager.GetInstance().FFProbePath, scene.Path) + tmpVideoFile, err := ffmpeg.NewVideoFile(manager.GetInstance().FFProbePath, scene.Path, false) if err != nil { logger.Errorf("[transcode] error reading video file: %s", err.Error()) return ffmpeg.Container("") @@ -100,7 +100,7 @@ func (rs sceneRoutes) StreamMp4(w http.ResponseWriter, r *http.Request) { func (rs sceneRoutes) StreamHLS(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) - videoFile, err := ffmpeg.NewVideoFile(manager.GetInstance().FFProbePath, scene.Path) + videoFile, err := ffmpeg.NewVideoFile(manager.GetInstance().FFProbePath, scene.Path, false) if err != nil { logger.Errorf("[stream] error reading video file: %s", err.Error()) return @@ -136,7 +136,7 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, vi // needs to be transcoded - videoFile, err := ffmpeg.NewVideoFile(manager.GetInstance().FFProbePath, scene.Path) + videoFile, err := ffmpeg.NewVideoFile(manager.GetInstance().FFProbePath, scene.Path, false) if err != nil { logger.Errorf("[stream] error reading video file: %s", err.Error()) return diff --git a/pkg/ffmpeg/ffprobe.go b/pkg/ffmpeg/ffprobe.go index d05ad2fd9..2dc1e2ad2 100644 --- a/pkg/ffmpeg/ffprobe.go +++ b/pkg/ffmpeg/ffprobe.go @@ -222,7 +222,7 @@ type VideoFile struct { } // Execute exec command and bind result to struct. -func NewVideoFile(ffprobePath string, videoPath string) (*VideoFile, error) { +func NewVideoFile(ffprobePath string, videoPath string, stripExt bool) (*VideoFile, error) { args := []string{"-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", "-show_error", videoPath} //// Extremely slow on windows for some reason //if runtime.GOOS != "windows" { @@ -239,10 +239,10 @@ func NewVideoFile(ffprobePath string, videoPath string) (*VideoFile, error) { return nil, fmt.Errorf("Error unmarshalling video data for <%s>: %s", videoPath, err.Error()) } - return parse(videoPath, probeJSON) + return parse(videoPath, probeJSON, stripExt) } -func parse(filePath string, probeJSON *FFProbeJSON) (*VideoFile, error) { +func parse(filePath string, probeJSON *FFProbeJSON, stripExt bool) (*VideoFile, error) { if probeJSON == nil { return nil, fmt.Errorf("failed to get ffprobe json for <%s>", filePath) } @@ -262,7 +262,7 @@ func parse(filePath string, probeJSON *FFProbeJSON) (*VideoFile, error) { if result.Title == "" { // default title to filename - result.SetTitleFromPath() + result.SetTitleFromPath(stripExt) } result.Comment = probeJSON.Format.Tags.Comment @@ -339,6 +339,11 @@ func (v *VideoFile) getStreamIndex(fileType string, probeJSON FFProbeJSON) int { return -1 } -func (v *VideoFile) SetTitleFromPath() { +func (v *VideoFile) SetTitleFromPath(stripExtension bool) { v.Title = filepath.Base(v.Path) + if stripExtension { + ext := filepath.Ext(v.Title) + v.Title = strings.TrimSuffix(v.Title, ext) + } + } diff --git a/pkg/manager/manager_tasks.go b/pkg/manager/manager_tasks.go index e1f69ed31..ace48ffc0 100644 --- a/pkg/manager/manager_tasks.go +++ b/pkg/manager/manager_tasks.go @@ -211,7 +211,7 @@ func (s *singleton) Scan(input models.ScanMetadataInput) { instance.Paths.Generated.EnsureTmpDir() wg.Add() - task := ScanTask{FilePath: path, UseFileMetadata: input.UseFileMetadata, fileNamingAlgorithm: fileNamingAlgo, calculateMD5: calculateMD5, GeneratePreview: input.ScanGeneratePreviews, GenerateImagePreview: input.ScanGenerateImagePreviews, GenerateSprite: input.ScanGenerateSprites} + task := ScanTask{FilePath: path, UseFileMetadata: input.UseFileMetadata, StripFileExtension: input.StripFileExtension, fileNamingAlgorithm: fileNamingAlgo, calculateMD5: calculateMD5, GeneratePreview: input.ScanGeneratePreviews, GenerateImagePreview: input.ScanGenerateImagePreviews, GenerateSprite: input.ScanGenerateSprites} go task.Start(&wg) return nil diff --git a/pkg/manager/scene.go b/pkg/manager/scene.go index ad8551112..d46be1dd3 100644 --- a/pkg/manager/scene.go +++ b/pkg/manager/scene.go @@ -174,7 +174,7 @@ func GetSceneFileContainer(scene *models.Scene) (ffmpeg.Container, error) { container = ffmpeg.Container(scene.Format.String) } else { // container isn't in the DB // shouldn't happen, fallback to ffprobe - tmpVideoFile, err := ffmpeg.NewVideoFile(GetInstance().FFProbePath, scene.Path) + tmpVideoFile, err := ffmpeg.NewVideoFile(GetInstance().FFProbePath, scene.Path, false) if err != nil { return ffmpeg.Container(""), fmt.Errorf("error reading video file: %s", err.Error()) } diff --git a/pkg/manager/task_generate_markers.go b/pkg/manager/task_generate_markers.go index 798ff21d0..e5fdf7683 100644 --- a/pkg/manager/task_generate_markers.go +++ b/pkg/manager/task_generate_markers.go @@ -34,7 +34,7 @@ func (t *GenerateMarkersTask) Start(wg *sizedwaitgroup.SizedWaitGroup) { return } - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path, false) if err != nil { logger.Errorf("error reading video file: %s", err.Error()) return @@ -51,7 +51,7 @@ func (t *GenerateMarkersTask) generateSceneMarkers() { return } - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path, false) if err != nil { logger.Errorf("error reading video file: %s", err.Error()) return diff --git a/pkg/manager/task_generate_preview.go b/pkg/manager/task_generate_preview.go index 30bd89945..839896c2f 100644 --- a/pkg/manager/task_generate_preview.go +++ b/pkg/manager/task_generate_preview.go @@ -30,7 +30,7 @@ func (t *GeneratePreviewTask) Start(wg *sizedwaitgroup.SizedWaitGroup) { return } - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path, false) if err != nil { logger.Errorf("error reading video file: %s", err.Error()) return diff --git a/pkg/manager/task_generate_screenshot.go b/pkg/manager/task_generate_screenshot.go index f6009fd82..73cdd3499 100644 --- a/pkg/manager/task_generate_screenshot.go +++ b/pkg/manager/task_generate_screenshot.go @@ -23,7 +23,7 @@ func (t *GenerateScreenshotTask) Start(wg *sync.WaitGroup) { defer wg.Done() scenePath := t.Scene.Path - probeResult, err := ffmpeg.NewVideoFile(instance.FFProbePath, scenePath) + probeResult, err := ffmpeg.NewVideoFile(instance.FFProbePath, scenePath, false) if err != nil { logger.Error(err.Error()) diff --git a/pkg/manager/task_generate_sprite.go b/pkg/manager/task_generate_sprite.go index f6ffc7ab8..20caeb78c 100644 --- a/pkg/manager/task_generate_sprite.go +++ b/pkg/manager/task_generate_sprite.go @@ -22,7 +22,7 @@ func (t *GenerateSpriteTask) Start(wg *sizedwaitgroup.SizedWaitGroup) { return } - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path, false) if err != nil { logger.Errorf("error reading video file: %s", err.Error()) return diff --git a/pkg/manager/task_scan.go b/pkg/manager/task_scan.go index b04e278b3..f3ac5299c 100644 --- a/pkg/manager/task_scan.go +++ b/pkg/manager/task_scan.go @@ -26,6 +26,7 @@ import ( type ScanTask struct { FilePath string UseFileMetadata bool + StripFileExtension bool calculateMD5 bool fileNamingAlgorithm models.HashAlgorithm GenerateSprite bool @@ -374,7 +375,7 @@ func (t *ScanTask) scanScene() *models.Scene { // check for container if !scene.Format.Valid { - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath, t.StripFileExtension) if err != nil { logger.Error(err.Error()) return nil @@ -455,7 +456,7 @@ func (t *ScanTask) scanScene() *models.Scene { return nil } - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath, t.StripFileExtension) if err != nil { logger.Error(err.Error()) return nil @@ -464,7 +465,7 @@ func (t *ScanTask) scanScene() *models.Scene { // Override title to be filename if UseFileMetadata is false if !t.UseFileMetadata { - videoFile.SetTitleFromPath() + videoFile.SetTitleFromPath(t.StripFileExtension) } var checksum string @@ -588,7 +589,7 @@ func (t *ScanTask) rescanScene(scene *models.Scene, fileModTime time.Time) (*mod } // regenerate the file details as well - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath, t.StripFileExtension) if err != nil { return nil, err } @@ -648,7 +649,7 @@ func (t *ScanTask) makeScreenshots(probeResult *ffmpeg.VideoFile, checksum strin if probeResult == nil { var err error - probeResult, err = ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath) + probeResult, err = ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath, t.StripFileExtension) if err != nil { logger.Error(err.Error()) diff --git a/pkg/manager/task_transcode.go b/pkg/manager/task_transcode.go index 44af08c48..f5a46e58f 100644 --- a/pkg/manager/task_transcode.go +++ b/pkg/manager/task_transcode.go @@ -30,7 +30,7 @@ func (t *GenerateTranscodeTask) Start(wg *sizedwaitgroup.SizedWaitGroup) { container = ffmpeg.Container(t.Scene.Format.String) } else { // container isn't in the DB // shouldn't happen unless user hasn't scanned after updating to PR#384+ version - tmpVideoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path) + tmpVideoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path, false) if err != nil { logger.Errorf("[transcode] error reading video file: %s", err.Error()) return @@ -49,7 +49,7 @@ func (t *GenerateTranscodeTask) Start(wg *sizedwaitgroup.SizedWaitGroup) { return } - videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path) + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path, false) if err != nil { logger.Errorf("[transcode] error reading video file: %s", err.Error()) return diff --git a/ui/v2.5/src/components/Changelog/versions/v050.md b/ui/v2.5/src/components/Changelog/versions/v050.md index d01bf1814..7dd01e09c 100644 --- a/ui/v2.5/src/components/Changelog/versions/v050.md +++ b/ui/v2.5/src/components/Changelog/versions/v050.md @@ -6,6 +6,7 @@ * Allow configuration of visible navbar items. ### 🎨 Improvements +* Add option to strip file extension from scene title when populating from scanning task. * Pagination support and general improvements for image lightbox. * Add mouse click support for CDP scrapers. * Add gallery tabs to performer and studio pages. diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx index fc4ee8604..89fddc33e 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx @@ -31,6 +31,7 @@ export const SettingsTasksPanel: React.FC = () => { const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); const [isScanDialogOpen, setIsScanDialogOpen] = useState(false); const [useFileMetadata, setUseFileMetadata] = useState(false); + const [stripFileExtension, setStripFileExtension] = useState(false); const [scanGeneratePreviews, setScanGeneratePreviews] = useState( false ); @@ -178,6 +179,7 @@ export const SettingsTasksPanel: React.FC = () => { await mutateMetadataScan({ paths, useFileMetadata, + stripFileExtension, scanGeneratePreviews, scanGenerateImagePreviews, scanGenerateSprites, @@ -317,6 +319,12 @@ export const SettingsTasksPanel: React.FC = () => { label="Set name, date, details from metadata (if present)" onChange={() => setUseFileMetadata(!useFileMetadata)} /> + setStripFileExtension(!stripFileExtension)} + />