Skip to content

Commit

Permalink
detect captions by default, updated filter, and database
Browse files Browse the repository at this point in the history
  • Loading branch information
cj12312021 committed May 4, 2022
1 parent 7be9076 commit 75e8bfb
Show file tree
Hide file tree
Showing 21 changed files with 36 additions and 92 deletions.
1 change: 0 additions & 1 deletion graphql/documents/data/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ fragment ConfigDefaultSettingsData on ConfigDefaultSettingsResult {
scan {
useFileMetadata
stripFileExtension
scanDetectCaptions
scanGeneratePreviews
scanGenerateImagePreviews
scanGenerateSprites
Expand Down
1 change: 0 additions & 1 deletion graphql/documents/data/scene-slim.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ fragment SlimSceneData on Scene {
interactive_speed
captions {
language_code
path
caption_type
}

Expand Down
1 change: 0 additions & 1 deletion graphql/documents/data/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ fragment SceneData on Scene {
interactive_speed
captions {
language_code
path
caption_type
}
created_at
Expand Down
4 changes: 2 additions & 2 deletions graphql/schema/types/filters.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ input SceneFilterType {
interactive: Boolean
"""Filter by InteractiveSpeed"""
interactive_speed: IntCriterionInput
"""Filter to only include scenes which have captions. `true` or `false`"""
captioned: String
"""Filter by captions"""
captions: StringCriterionInput
}

input MovieFilterType {
Expand Down
4 changes: 0 additions & 4 deletions graphql/schema/types/metadata.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ input ScanMetadataInput {
scanGeneratePhashes: Boolean
"""Generate image thumbnails during scan"""
scanGenerateThumbnails: Boolean
"""Detect scene captions"""
scanDetectCaptions: Boolean

"Filter options for the scan"
filter: ScanMetaDataFilterInput
Expand All @@ -107,8 +105,6 @@ type ScanMetadataOptions {
scanGeneratePhashes: Boolean!
"""Generate image thumbnails during scan"""
scanGenerateThumbnails: Boolean!
"""Detect scene captions"""
scanDetectCaptions: Boolean!
}

input CleanMetadataInput {
Expand Down
1 change: 0 additions & 1 deletion graphql/schema/types/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ type SceneMovie {

type SceneCaption {
language_code: String!
path: String!
caption_type: String!
}

Expand Down
5 changes: 2 additions & 3 deletions internal/api/routes_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,14 @@ func (rs sceneRoutes) InteractiveHeatmap(w http.ResponseWriter, r *http.Request)
func (rs sceneRoutes) Caption(w http.ResponseWriter, r *http.Request, lang string, ext string) {
s := r.Context().Value(sceneKey).(*models.Scene)

path := ""
if err := rs.txnManager.WithReadTxn(r.Context(), func(repo models.ReaderRepository) error {
var err error
captions, err := repo.Scene().GetCaptions(s.ID)
for _, caption := range captions {
if lang == caption.LanguageCode && ext == caption.CaptionType {
path = caption.Path
captionPath := scene.GetCaptionPath(s.Path, caption)

sub, err := scene.ReadSubs(path)
sub, err := scene.ReadSubs(captionPath)
if err == nil {
var b bytes.Buffer
err = sub.WriteToWebVTT(&b)
Expand Down
4 changes: 1 addition & 3 deletions internal/manager/task_scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ func (j *ScanJob) Execute(ctx context.Context, progress *job.Progress) {
file: file.FSFile(f.path, f.info),
UseFileMetadata: utils.IsTrue(input.UseFileMetadata),
StripFileExtension: utils.IsTrue(input.StripFileExtension),
DetectCaptions: utils.IsTrue(input.ScanDetectCaptions),
fileNamingAlgorithm: fileNamingAlgo,
calculateMD5: calculateMD5,
GeneratePreview: utils.IsTrue(input.ScanGeneratePreviews),
Expand Down Expand Up @@ -254,7 +253,6 @@ type ScanTask struct {
file file.SourceFile
UseFileMetadata bool
StripFileExtension bool
DetectCaptions bool
calculateMD5 bool
fileNamingAlgorithm models.HashAlgorithm
GenerateSprite bool
Expand All @@ -280,7 +278,7 @@ func (t *ScanTask) Start(ctx context.Context) {
s = t.scanScene(ctx)
case isImage(path):
t.scanImage(ctx)
case t.DetectCaptions && isCaptions(path):
case isCaptions(path):
t.associateCaptions(ctx)
}
})
Expand Down
2 changes: 0 additions & 2 deletions internal/manager/task_scan_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ func (t *ScanTask) scanScene(ctx context.Context) *models.Scene {
PluginCache: instance.PluginCache,
MutexManager: t.mutexManager,
UseFileMetadata: t.UseFileMetadata,
DetectCaptions: t.DetectCaptions,
}

if s != nil {
Expand Down Expand Up @@ -109,7 +108,6 @@ func (t *ScanTask) associateCaptions(ctx context.Context) {
if !scene.IsLangInCaptions(captionLang, ext, captions) { // only update captions if language code is not present
newCaption := &models.SceneCaption{
LanguageCode: captionLang,
Path: captionPath,
CaptionType: ext,
}
captions = append(captions, newCaption)
Expand Down
1 change: 0 additions & 1 deletion pkg/database/migrations/31_scenes_captions.up.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
CREATE TABLE `scene_captions` (
`scene_id` integer,
`language_code` varchar(255) NOT NULL,
`path` varchar(255) NOT NULL,
`caption_type` varchar(255) NOT NULL,
foreign key(`scene_id`) references `scenes`(`id`) on delete CASCADE
);
18 changes: 6 additions & 12 deletions pkg/scene/caption.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@ const LangUknown = "00"

// GetCaptionPath generates the path of a caption
// from a given file path, wanted language and caption sufffix
func GetCaptionPath(path, lang, suffix string) string {
ext := filepath.Ext(path)
fn := strings.TrimSuffix(path, ext)
captionExt := ""
if len(lang) == 0 || lang == LangUknown {
captionExt = suffix
} else {
captionExt = lang + "." + suffix
}
return fn + "." + captionExt
func GetCaptionPath(scenePath string, caption *models.SceneCaption) string {
sceneExt := filepath.Ext(scenePath)
sceneName := strings.TrimSuffix(scenePath, sceneExt)
return sceneName + "." + caption.LanguageCode + "." + caption.CaptionType
}

// ReadSubs reads a captions file
Expand Down Expand Up @@ -89,11 +83,11 @@ func GetCaptionsLangFromPath(captionPath string) string {
}

// CleanCaptions removes non existent/accessible language codes from captions
func CleanCaptions(captions []*models.SceneCaption) (cleanedCaptions []*models.SceneCaption, changed bool) {
func CleanCaptions(scenePath string, captions []*models.SceneCaption) (cleanedCaptions []*models.SceneCaption, changed bool) {
changed = false
for _, caption := range captions {
found := false
f := caption.Path
f := GetCaptionPath(scenePath, caption)
if _, er := os.Stat(f); er == nil {
cleanedCaptions = append(cleanedCaptions, caption)
found = true
Expand Down
5 changes: 2 additions & 3 deletions pkg/scene/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ type Scanner struct {

StripFileExtension bool
UseFileMetadata bool
DetectCaptions bool
FileNamingAlgorithm models.HashAlgorithm

CaseSensitiveFs bool
Expand Down Expand Up @@ -112,8 +111,8 @@ func (scanner *Scanner) ScanExisting(ctx context.Context, existing file.FileBase

captions, er := sqb.GetCaptions(s.ID)
if er == nil {
if len(captions) > 0 && scanner.DetectCaptions {
clean, altered := CleanCaptions(captions)
if len(captions) > 0 {
clean, altered := CleanCaptions(s.Path, captions)
if altered {
er = sqb.UpdateCaptions(s.ID, clean)
if er == nil {
Expand Down
11 changes: 4 additions & 7 deletions pkg/sqlite/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,25 +370,22 @@ func (r *imageRepository) replace(id int, image []byte) error {
type captionRepository struct {
repository
captionCodeColumn string
captionPathColumn string
captionTypeColumn string
}

func (r *captionRepository) get(id int) ([]*models.SceneCaption, error) {
query := fmt.Sprintf("SELECT %s, %s, %s from %s WHERE %s = ?", r.captionCodeColumn, r.captionPathColumn, r.captionTypeColumn, r.tableName, r.idColumn)
query := fmt.Sprintf("SELECT %s, %s from %s WHERE %s = ?", r.captionCodeColumn, r.captionTypeColumn, r.tableName, r.idColumn)
var ret []*models.SceneCaption
err := r.queryFunc(query, []interface{}{id}, false, func(rows *sqlx.Rows) error {
var captionCodeColumn string
var captionPathColumn string
var captionTypeColumn string

if err := rows.Scan(&captionCodeColumn, &captionPathColumn, &captionTypeColumn); err != nil {
if err := rows.Scan(&captionCodeColumn, &captionTypeColumn); err != nil {
return err
}

caption := &models.SceneCaption{
LanguageCode: captionCodeColumn,
Path: captionPathColumn,
CaptionType: captionTypeColumn,
}
ret = append(ret, caption)
Expand All @@ -398,8 +395,8 @@ func (r *captionRepository) get(id int) ([]*models.SceneCaption, error) {
}

func (r *captionRepository) insert(id int, caption *models.SceneCaption) (sql.Result, error) {
stmt := fmt.Sprintf("INSERT INTO %s (%s, %s, %s, %s) VALUES (?, ?, ?, ?)", r.tableName, r.idColumn, r.captionCodeColumn, r.captionPathColumn, r.captionTypeColumn)
return r.tx.Exec(stmt, id, caption.LanguageCode, caption.Path, caption.CaptionType)
stmt := fmt.Sprintf("INSERT INTO %s (%s, %s, %s) VALUES (?, ?, ?)", r.tableName, r.idColumn, r.captionCodeColumn, r.captionTypeColumn)
return r.tx.Exec(stmt, id, caption.LanguageCode, caption.CaptionType)
}

func (r *captionRepository) replace(id int, captions []*models.SceneCaption) error {
Expand Down
30 changes: 14 additions & 16 deletions pkg/sqlite/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const moviesScenesTable = "movies_scenes"

const sceneCaptionsTable = "scene_captions"
const sceneCaptionCodeColumn = "language_code"
const sceneCaptionPathColumn = "path"
const sceneCaptionTypeColumn = "caption_type"

var scenesForPerformerQuery = selectAll(sceneTable) + `
Expand Down Expand Up @@ -140,7 +139,6 @@ func (qb *sceneQueryBuilder) captionRepository() *captionRepository {
idColumn: sceneIDColumn,
},
captionCodeColumn: sceneCaptionCodeColumn,
captionPathColumn: sceneCaptionPathColumn,
captionTypeColumn: sceneCaptionTypeColumn,
}
}
Expand Down Expand Up @@ -399,7 +397,6 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
query.handleCriterion(durationCriterionHandler(sceneFilter.Duration, "scenes.duration"))
query.handleCriterion(resolutionCriterionHandler(sceneFilter.Resolution, "scenes.height", "scenes.width"))
query.handleCriterion(hasMarkersCriterionHandler(sceneFilter.HasMarkers))
query.handleCriterion(captionedCriterionHandler(sceneFilter.Captioned))
query.handleCriterion(sceneIsMissingCriterionHandler(qb, sceneFilter.IsMissing))
query.handleCriterion(stringCriterionHandler(sceneFilter.URL, "scenes.url"))

Expand All @@ -413,6 +410,8 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
query.handleCriterion(boolCriterionHandler(sceneFilter.Interactive, "scenes.interactive"))
query.handleCriterion(intCriterionHandler(sceneFilter.InteractiveSpeed, "scenes.interactive_speed"))

query.handleCriterion(sceneCaptionCriterionHandler(qb, sceneFilter.Captions))

query.handleCriterion(sceneTagsCriterionHandler(qb, sceneFilter.Tags))
query.handleCriterion(sceneTagCountCriterionHandler(qb, sceneFilter.TagCount))
query.handleCriterion(scenePerformersCriterionHandler(qb, sceneFilter.Performers))
Expand Down Expand Up @@ -594,19 +593,6 @@ func hasMarkersCriterionHandler(hasMarkers *string) criterionHandlerFunc {
}
}

func captionedCriterionHandler(captioned *string) criterionHandlerFunc {
return func(f *filterBuilder) {
if captioned != nil {
f.addLeftJoin("scene_captions", "", "scene_captions.scene_id = scenes.id")
if *captioned == "true" {
f.addHaving("count(scene_captions.scene_id) > 0")
} else {
f.addWhere("scene_captions.path IS NULL")
}
}
}
}

func sceneIsMissingCriterionHandler(qb *sceneQueryBuilder, isMissing *string) criterionHandlerFunc {
return func(f *filterBuilder) {
if isMissing != nil && *isMissing != "" {
Expand Down Expand Up @@ -648,6 +634,18 @@ func (qb *sceneQueryBuilder) getMultiCriterionHandlerBuilder(foreignTable, joinT
}
}

func sceneCaptionCriterionHandler(qb *sceneQueryBuilder, captions *models.StringCriterionInput) criterionHandlerFunc {
h := stringListCriterionHandlerBuilder{
joinTable: sceneCaptionsTable,
stringColumn: sceneCaptionCodeColumn,
addJoinTable: func(f *filterBuilder) {
qb.captionRepository().join(f, "", "scenes.id")
},
}

return h.handler(captions)
}

func sceneTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.HierarchicalMultiCriterionInput) criterionHandlerFunc {
h := joinedHierarchicalMultiCriterionHandlerBuilder{
tx: qb.tx,
Expand Down
7 changes: 0 additions & 7 deletions ui/v2.5/src/components/Settings/Tasks/ScanOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ export const ScanOptions: React.FC<IScanOptions> = ({
const {
useFileMetadata,
stripFileExtension,
scanDetectCaptions,
scanGeneratePreviews,
scanGenerateImagePreviews,
scanGenerateSprites,
Expand Down Expand Up @@ -76,12 +75,6 @@ export const ScanOptions: React.FC<IScanOptions> = ({
headingID="config.tasks.set_name_date_details_from_metadata_if_present"
onChange={(v) => setOptions({ useFileMetadata: v })}
/>
<BooleanSetting
id="scan-detect-subtitles"
checked={scanDetectCaptions ?? false}
headingID="config.tasks.detect_captions_during_scan"
onChange={(v) => setOptions({ scanDetectCaptions: v })}
/>
</>
);
};
2 changes: 0 additions & 2 deletions ui/v2.5/src/docs/en/Captions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@ These files need to be named as follows:

Where `{language_code}` is defined by the [ISO-6399-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (2 letters) standard and `ext` is the file extension. Captions files without a language code will be labeled as Unknown in the video player but will work fine.

To enable detection of these files during a scan, make sure to toggle the `Search for caption files` scan option on.

Scenes with captions can be filtered with the `captions` criterion.
3 changes: 1 addition & 2 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,6 @@
"cleanup_desc": "Check for missing files and remove them from the database. This is a destructive action.",
"data_management": "Data management",
"defaults_set": "Defaults have been set and will be used when clicking the {action} button on the Tasks page.",
"detect_captions_during_scan": "Search for caption files",
"dont_include_file_extension_as_part_of_the_title": "Don't include file extension as part of the title",
"empty_queue": "No tasks are currently running.",
"export_to_json": "Exports the database content into JSON format in the metadata directory.",
Expand Down Expand Up @@ -744,7 +743,7 @@
"instagram": "Instagram",
"interactive": "Interactive",
"interactive_speed": "Interactive speed",
"captioned": "Captioned",
"captions": "Captions",
"isMissing": "Is Missing",
"library": "Library",
"loading": {
Expand Down
18 changes: 0 additions & 18 deletions ui/v2.5/src/models/list-filter/criteria/captioned.ts

This file was deleted.

5 changes: 2 additions & 3 deletions ui/v2.5/src/models/list-filter/criteria/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
} from "./criterion";
import { OrganizedCriterion } from "./organized";
import { FavoriteCriterion, PerformerFavoriteCriterion } from "./favorite";
import { CaptionedCriterion } from "./captioned";
import { HasMarkersCriterion } from "./has-markers";
import {
PerformerIsMissingCriterionOption,
Expand Down Expand Up @@ -160,8 +159,8 @@ export function makeCriteria(type: CriterionType = "none") {
return new StringCriterion(new StringCriterionOption(type, type));
case "interactive":
return new InteractiveCriterion();
case "captioned":
return new CaptionedCriterion();
case "captions":
return new StringCriterion(new StringCriterionOption(type, type));
case "parent_tag_count":
return new NumberCriterion(
new MandatoryNumberCriterionOption(
Expand Down
3 changes: 1 addition & 2 deletions ui/v2.5/src/models/list-filter/scenes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
createMandatoryStringCriterionOption,
createStringCriterionOption,
} from "./criteria/criterion";
import { CaptionedCriterionOption } from "./criteria/captioned";
import { HasMarkersCriterionOption } from "./criteria/has-markers";
import { SceneIsMissingCriterionOption } from "./criteria/is-missing";
import { MoviesCriterionOption } from "./criteria/movies";
Expand Down Expand Up @@ -79,7 +78,7 @@ const criterionOptions = [
createStringCriterionOption("url"),
createStringCriterionOption("stash_id"),
InteractiveCriterionOption,
CaptionedCriterionOption,
createStringCriterionOption("captions"),
createMandatoryNumberCriterionOption("interactive_speed"),
];

Expand Down
Loading

0 comments on commit 75e8bfb

Please sign in to comment.