Skip to content

Commit 3cb0434

Browse files
authored
Added two point alignment mods
1 parent 7efbe86 commit 3cb0434

File tree

10 files changed

+440
-4
lines changed

10 files changed

+440
-4
lines changed

bazarr/api/subtitles/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
from .subtitles import api_ns_subtitles
44
from .subtitles_info import api_ns_subtitles_info
5+
from .subtitles_contents import api_ns_subtitle_contents
56

67

78
api_ns_list_subtitles = [
89
api_ns_subtitles,
910
api_ns_subtitles_info,
11+
api_ns_subtitle_contents
1012
]
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# coding=utf-8
2+
import logging
3+
import srt
4+
5+
from flask_restx import Resource, Namespace, reqparse, fields, marshal
6+
7+
from ..utils import authenticate
8+
9+
10+
api_ns_subtitle_contents = Namespace('Subtitle Contents', description='Retrieve contents of subtitle file')
11+
12+
13+
@api_ns_subtitle_contents.route('subtitles/contents')
14+
class SubtitleNameContents(Resource):
15+
get_request_parser = reqparse.RequestParser()
16+
get_request_parser.add_argument('subtitlePath', type=str, required=True, help='Subtitle filepath')
17+
18+
time_modal = api_ns_subtitle_contents.model('time_modal', {
19+
'hours': fields.Integer(),
20+
'minutes': fields.Integer(),
21+
'seconds': fields.Integer(),
22+
'total_seconds': fields.Integer(),
23+
'microseconds': fields.Integer(),
24+
})
25+
26+
get_response_model = api_ns_subtitle_contents.model('SubtitlesContentsGetResponse', {
27+
'index': fields.Integer(),
28+
'content': fields.String(),
29+
'proprietary': fields.String(),
30+
'start': fields.Nested(time_modal),
31+
'end': fields.Nested(time_modal),
32+
# 'duration': fields.Nested(time_modal),
33+
})
34+
35+
@authenticate
36+
@api_ns_subtitle_contents.response(200, 'Success')
37+
@api_ns_subtitle_contents.response(401, 'Not Authenticated')
38+
@api_ns_subtitle_contents.doc(parser=get_request_parser)
39+
def get(self):
40+
"""Retrieve subtitle file contents"""
41+
42+
args = self.get_request_parser.parse_args()
43+
path = args.get('subtitlePath')
44+
45+
results = []
46+
47+
# Load the SRT content
48+
with open(path, "r", encoding="utf-8") as f:
49+
file_content = f.read()
50+
51+
# Map contents
52+
for sub in srt.parse(file_content):
53+
54+
start_total_seconds = int(sub.start.total_seconds())
55+
end_total_seconds = int(sub.end.total_seconds())
56+
duration_timedelta = sub.end - sub.start
57+
58+
results.append(dict(
59+
index=sub.index,
60+
content=sub.content,
61+
proprietary=sub.proprietary,
62+
start=dict(
63+
hours = start_total_seconds // 3600,
64+
minutes = (start_total_seconds % 3600) // 60,
65+
seconds = start_total_seconds % 60,
66+
total_seconds=int(sub.start.total_seconds()),
67+
microseconds = sub.start.microseconds
68+
),
69+
end=dict(
70+
hours = end_total_seconds // 3600,
71+
minutes = (end_total_seconds % 3600) // 60,
72+
seconds = end_total_seconds % 60,
73+
total_seconds=int(sub.end.total_seconds()),
74+
microseconds = sub.end.microseconds
75+
),
76+
# duration=dict(
77+
# seconds=int(duration_timedelta.total_seconds()),
78+
# microseconds=duration_timedelta.microseconds
79+
# ),
80+
))
81+
82+
return marshal(results, self.get_response_model, envelope='data')

custom_libs/subzero/modification/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33
from __future__ import absolute_import
44
from .registry import registry
5-
from .mods import hearing_impaired, ocr_fixes, fps, offset, common, color, emoji
5+
from .mods import hearing_impaired, ocr_fixes, fps, offset, common, color, emoji, two_point_fit
66
from .main import SubtitleModifications, SubMod
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# coding=utf-8
2+
3+
from __future__ import absolute_import
4+
import logging
5+
6+
from subzero.modification.mods import SubtitleModification
7+
from subzero.modification import registry
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class TwoPointFit(SubtitleModification):
13+
identifier = "two_point_fit"
14+
description = "Use first and last sentences to linearly align timing of the subtitles"
15+
exclusive = False
16+
advanced = True
17+
modifies_whole_file = True
18+
19+
def modify(self, content, debug=False, parent=None, **kwargs):
20+
21+
"""
22+
Place first sentence at 00:00:00 and scale until duration matches, then offset back
23+
"""
24+
25+
parent.f.shift(h=-int(kwargs.get("rh", 0)), m=-int(kwargs.get("rm", 0)), s=-int(kwargs.get("rs", 0)), ms=-int(kwargs.get("rms", 0)))
26+
parent.f.transform_framerate(float(kwargs.get("from")), float(kwargs.get("to")))
27+
parent.f.shift(h=int(kwargs.get("oh", 0)), m=int(kwargs.get("om", 0)), s=int(kwargs.get("os", 0)), ms=int(kwargs.get("oms", 0)))
28+
29+
registry.register(TwoPointFit)

frontend/src/apis/hooks/subtitles.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,14 @@ export function useSubtitleInfos(names: string[]) {
146146
});
147147
}
148148

149+
export function useSubtitleContents(subtitlePath: string) {
150+
return useQuery({
151+
queryKey: [QueryKeys.Subtitles, subtitlePath],
152+
queryFn: () => api.subtitles.contents(subtitlePath),
153+
staleTime: Infinity,
154+
});
155+
}
156+
149157
export function useRefTracksByEpisodeId(
150158
subtitlesPath: string,
151159
sonarrEpisodeId: number,

frontend/src/apis/raw/subtitles.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ class SubtitlesApi extends BaseApi {
3737
async modify(action: string, form: FormType.ModifySubtitle) {
3838
await this.patch("", form, { action });
3939
}
40+
41+
async contents(subtitlePath: string) {
42+
const response = await this.get<DataWrapper<SubtitleContents.Line[]>>(
43+
"/contents",
44+
{
45+
subtitlePath,
46+
},
47+
);
48+
return response.data;
49+
}
4050
}
4151

4252
const subtitlesApi = new SubtitlesApi();

frontend/src/components/SubtitleToolsMenu.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FunctionComponent, ReactElement, useCallback, useMemo } from "react";
22
import { Divider, List, Menu, MenuProps, ScrollArea } from "@mantine/core";
33
import {
4+
faAlignJustify,
45
faClock,
56
faCode,
67
faDeaf,
@@ -27,6 +28,7 @@ import { useModals } from "@/modules/modals";
2728
import { ModalComponent } from "@/modules/modals/WithModal";
2829
import { task } from "@/modules/task";
2930
import { SyncSubtitleModal } from "./forms/SyncSubtitleForm";
31+
import { TwoPointFitModal } from "./forms/TwoPointFit";
3032

3133
export interface ToolOptions {
3234
key: string;
@@ -99,6 +101,12 @@ export function useTools() {
99101
name: "Adjust Times...",
100102
modal: TimeOffsetModal,
101103
},
104+
{
105+
key: "two_point_fit",
106+
icon: faAlignJustify,
107+
name: "Two-Point Fit...",
108+
modal: TwoPointFitModal,
109+
},
102110
{
103111
key: "translation",
104112
icon: faLanguage,

0 commit comments

Comments
 (0)