Skip to content

Commit 413ce30

Browse files
authored
Merge pull request #34 from jessielw/rc2
Rc2
2 parents 240861f + 01d2497 commit 413ce30

File tree

25 files changed

+985
-128
lines changed

25 files changed

+985
-128
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ name: Build Desktop
22

33
on:
44
push:
5-
# tags:
6-
# - 'v*'
5+
branches: ["main"]
6+
release:
7+
types: [published]
78
workflow_dispatch:
89

910
jobs:

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Thumbs.db
2525
# misc
2626
.scraps/
2727

28-
# runtime
29-
runtime/
28+
# runtime (ignore everything except images)
29+
runtime/*
3030
!runtime/images/
31+
!runtime/.gitkeep

CHANGELOG.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,47 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.0.0-rc2] - 2025-12-07
9+
10+
### Added
11+
12+
- Pop up for failure to load MediaInfo
13+
- Multiple track import when opening a video on the **Video** tab:
14+
- New window to select which track(s) a user would like to import
15+
- This supports audio(s), subtitle(s) and chapters
16+
- Error handling when a file is opened for video file without a video track
17+
- Now detects language _(based off of MediaInfo)_ and applies it from input
18+
- Now detects title and applies it from input
19+
- Now detects default/forced flags where applicable and applies them from mediainfo if being imported from the video tab
20+
- Default flags to audio/subtitles
21+
- Automatically unchecks other default flags if checked in another tab in the same category _(category: audio/subtitles)_
22+
- Forced flags to subtitles
23+
- Audio/subtitles are now importable from mp4/m4v files
24+
- Button to see where log files are stored in the settings panel
25+
- Now automatically cleans up log files over 50 log files ~1 seconds after UI initializes
26+
- Added a **Details** column in the **Output** tab that will allow the user to click on it in the event of a failure and see the output from mp4box when a job fails
27+
- Remembers last opened path in the context to open new file browsers at that same path
28+
- Generates a default output name based on the input from the video tab _(file input_new.mp4)_
29+
- Asks to overwrite if file already exists on adding new job to queue
30+
- Now attempts to detects language from filename as a fallback if mediainfo doesn't have it
31+
- Detects forced/foreign in subtitle filename and auto checks the track in the UI if input is not mp4/m4v
32+
33+
### Changed
34+
35+
- Subtitle tab now accepts **.mp4/.m4v**
36+
- Improved logging (still some work to be done)
37+
- All tabs contents are now wrapped in a scrolled area box - allowing the program to stay compact but also have larger default widget sizes if needed
38+
- Changed to a slightly better icon for the program (still could use improvement)
39+
- Error window pop can now be maximized
40+
41+
### Fixed
42+
43+
- Settings mp4box path was set to the wrong icon
44+
45+
### Removed
46+
47+
-
48+
849
## [2.0.0-rc1] - 2025-12-07
950

1051
### Added

build_desktop.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def build_app():
3636

3737
# define paths before changing directory
3838
desktop_script = Path(project_root / "frontend_desktop" / "main.py")
39-
icon_path = Path(dev_runtime / "images" / "mp4mux.ico")
39+
icon_path = Path(dev_runtime / "images" / "mp4.ico")
4040

4141
# get extra deps
4242
# site_packages = get_site_packages()

core/job_states.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class SubtitleState:
6161
title: str = ""
6262
default: bool = False
6363
forced: bool = False
64+
track_id: int | None = None # for multi-track MP4 inputs
6465

6566
def to_dict(self) -> dict:
6667
"""Convert to dictionary for serialization."""
@@ -70,6 +71,7 @@ def to_dict(self) -> dict:
7071
"title": self.title,
7172
"default": self.default,
7273
"forced": self.forced,
74+
"track_id": self.track_id,
7375
}
7476

7577

core/muxer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,13 @@ def _mux_with_mp4box(self, job: MuxJob) -> None:
189189
# add subtitle tracks with default/forced logic
190190
subtitle_defaults_set = any(sub.default for sub in job.subtitle_tracks)
191191
for subtitle in job.subtitle_tracks:
192-
subtitle_opts = ""
192+
# determine track selector (for multi-track MP4 inputs)
193+
if subtitle.track_id is not None:
194+
track_selector = f"#{subtitle.track_id}"
195+
else:
196+
track_selector = "#text" # default to first text track
197+
198+
subtitle_opts = track_selector
193199
if subtitle.language:
194200
subtitle_opts += f":lang={subtitle.language.part3}"
195201
subtitle_opts += (

core/utils/autoqpf.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from pathlib import Path
22

3+
from auto_qpf.enums import ChapterType
34
from auto_qpf.generate_chapters import ChapterGenerator
45
from pymediainfo import MediaInfo
56

@@ -13,3 +14,27 @@ def auto_gen_chapters(mi: MediaInfo) -> str | None:
1314
if isinstance(chapters, Path):
1415
raise RuntimeError("Chapters should only be str or None")
1516
return chapters
17+
18+
19+
def determine_chapter_type(mi: MediaInfo) -> str | None:
20+
"""Determine chapter type based on MediaInfo."""
21+
mi_chaps = ChapterGenerator()._get_media_info_obj_chapters(mi)
22+
if not mi_chaps:
23+
return None
24+
chapters_data = ChapterGenerator()._determine_chapter_type(mi_chaps)
25+
if chapters_data:
26+
try:
27+
chap_enum = chapters_data[0]
28+
if chap_enum is ChapterType.NUMBERED and len(chapters_data) >= 4:
29+
chap_num_details = f" ({chapters_data[2]} - {chapters_data[3]})"
30+
else:
31+
chap_num_details = ""
32+
chap_type_str_map = {
33+
ChapterType.NAMED: "Named",
34+
ChapterType.NUMBERED: f"Numbered{chap_num_details}",
35+
ChapterType.TAGGED: "Tagged",
36+
}
37+
38+
return chap_type_str_map[chap_enum]
39+
except Exception:
40+
return None

core/utils/language.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import re
2+
13
from iso639 import Language, LanguageNotFoundError
24
from pymediainfo import Track
35

@@ -88,3 +90,54 @@ def get_language_obj(language_str: str) -> Language | None:
8890
except LanguageNotFoundError:
8991
return None
9092
return None
93+
94+
95+
def detect_language_from_filename(filename: str) -> Language | None:
96+
"""Detect language code from filename by searching for common patterns.
97+
98+
Looks for ISO 639-1 (2-letter), ISO 639-2 (3-letter), and common language names.
99+
Prioritizes: naturally lowercase > naturally UPPERCASE > mixed case words
100+
101+
Args:
102+
filename (str): The filename to search for language codes
103+
104+
Returns:
105+
Language | None: Matched Language object or None
106+
"""
107+
# remove extension but keep original case
108+
name_no_ext = filename.rsplit(".", 1)[0]
109+
110+
# common patterns: "movie.eng.srt", "movie_eng_sub.srt", "movie.en.srt", etc.
111+
# look for word boundaries or underscores/dots around language codes
112+
patterns = [
113+
r"[._-]([a-zA-Z]{2,3})[._-]", # .eng. or _en_ or -jpn-
114+
r"[._-]([a-zA-Z]{2,3})$", # .eng or _en at end
115+
r"^([a-zA-Z]{2,3})[._-]", # eng. or en_ at start
116+
]
117+
118+
# collect all candidates with their case type
119+
candidates = []
120+
for pattern in patterns:
121+
matches = re.finditer(pattern, name_no_ext)
122+
for match in matches:
123+
lang_code = match.group(1)
124+
# determine case type for priority
125+
if lang_code.islower():
126+
priority = 0 # highest priority
127+
elif lang_code.isupper():
128+
priority = 1 # medium priority
129+
else:
130+
priority = 2 # lowest priority (mixed case)
131+
candidates.append((priority, lang_code))
132+
133+
# sort by priority (lower number = higher priority)
134+
candidates.sort(key=lambda x: x[0])
135+
136+
# try to match in priority order
137+
for _, lang_code in candidates:
138+
try:
139+
return Language.match(lang_code.lower())
140+
except LanguageNotFoundError:
141+
continue
142+
143+
return None

frontend_desktop/context.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from dataclasses import dataclass
2+
from pathlib import Path
3+
4+
5+
@dataclass
6+
class Context:
7+
"""Holds shared context data for the frontend desktop application."""
8+
9+
last_used_path: Path | None = None
10+
11+
12+
context = Context()

frontend_desktop/global_signals.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ class GlobalSignals(QObject):
1717
# chapters
1818
chapters_updated = Signal(str)
1919

20+
# video audio tracks
21+
video_audio_tracks_detected = Signal(
22+
object, object, list
23+
) # MediaInfo, Path, selected_track_ids
24+
25+
# video subtitle tracks
26+
video_subtitle_tracks_detected = Signal(
27+
object, object, list
28+
) # MediaInfo, Path, selected_track_ids
29+
30+
# video generate output filepath
31+
video_generate_output_filepath = Signal(object) # suggested Path
32+
2033
# scaling # TODO: add this feature in
2134
# scale_factor_changed_by_user = Signal(float) # user hotkey changes (auto-save)
2235
# scale_factor_changed = Signal(float) # all changes (UI sync)

0 commit comments

Comments
 (0)