Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
142 changes: 139 additions & 3 deletions Base.lproj/QueueOptions.xib
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="UlG-O3-eNc" userLabel="OptionsView">
<rect key="frame" x="0.0" y="0.0" width="382" height="642"/>
<rect key="frame" x="0.0" y="0.0" width="382" height="732"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<button verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="zEH-aV-Tl7">
Expand Down Expand Up @@ -555,6 +555,127 @@
</binding>
</connections>
</button>
<button toolTip="Insert chapter markers at the selected interval" verticalHuggingPriority="751" translatesAutoresizingMaskIntoConstraints="NO" id="chAdd-btn">
<rect key="frame" x="111" y="320" width="155" height="16"/>
<buttonCell key="cell" type="check" title="Add chapters at intervals" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" inset="2" id="chAdd-cell">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="label" size="11"/>
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.options.addChapters" id="chAdd-bind">
<dictionary key="options">
<bool key="NSValidatesImmediately" value="YES"/>
</dictionary>
</binding>
</connections>
</button>
<textField focusRingType="none" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="200" translatesAutoresizingMaskIntoConstraints="NO" id="chInt-lbl">
<rect key="frame" x="126" y="302" width="55" height="14"/>
<constraints>
<constraint firstAttribute="width" constant="51" id="chInt-w"/>
</constraints>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Interval:" id="chInt-cell">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="enabled" keyPath="self.options.addChapters" id="chInt-en"/>
</connections>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="chInt-pop">
<rect key="frame" x="188" y="300" width="111" height="17"/>
<popUpButtonCell key="cell" type="roundRect" title="5 minutes" bezelStyle="roundedRect" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="5" imageScaling="proportionallyDown" inset="2" selectedItem="chInt-5" id="chInt-pop-cell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="label" size="11"/>
<menu key="menu" id="chInt-menu">
<items>
<menuItem title="1 minute" tag="1" id="chInt-1"/>
<menuItem title="2 minutes" tag="2" id="chInt-2"/>
<menuItem title="5 minutes" state="on" tag="5" id="chInt-5"/>
<menuItem title="10 minutes" tag="10" id="chInt-10"/>
<menuItem title="15 minutes" tag="15" id="chInt-15"/>
<menuItem title="20 minutes" tag="20" id="chInt-20"/>
<menuItem title="30 minutes" tag="30" id="chInt-30"/>
</items>
</menu>
</popUpButtonCell>
<constraints>
<constraint firstAttribute="width" constant="111" id="chInt-pw"/>
</constraints>
<connections>
<accessibilityConnection property="title" destination="chInt-lbl" id="chInt-acc"/>
<binding destination="-2" name="selectedTag" keyPath="options.addChaptersInterval" id="chInt-tag"/>
<binding destination="-2" name="enabled" keyPath="self.options.addChapters" id="chInt-pen"/>
</connections>
</popUpButton>
<textField focusRingType="none" verticalHuggingPriority="750" allowsCharacterPickerTouchBarItem="YES" preferredMaxLayoutWidth="200" translatesAutoresizingMaskIntoConstraints="NO" id="chPol-lbl">
<rect key="frame" x="126" y="282" width="55" height="14"/>
<constraints>
<constraint firstAttribute="width" constant="51" id="chPol-w"/>
</constraints>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Policy:" id="chPol-cell">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="enabled" keyPath="self.options.addChapters" id="chPol-en"/>
</connections>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="chPol-pop">
<rect key="frame" x="188" y="280" width="140" height="17"/>
<popUpButtonCell key="cell" type="roundRect" title="Replace existing" bezelStyle="roundedRect" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="border" tag="0" imageScaling="proportionallyDown" inset="2" selectedItem="chPol-rep" id="chPol-pop-cell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="label" size="11"/>
<menu key="menu" id="chPol-menu">
<items>
<menuItem title="Replace existing" state="on" tag="0" id="chPol-rep"/>
<menuItem title="Merge existing" tag="1" id="chPol-mrg"/>
</items>
</menu>
</popUpButtonCell>
<constraints>
<constraint firstAttribute="width" constant="140" id="chPol-pw"/>
</constraints>
<connections>
<accessibilityConnection property="title" destination="chPol-lbl" id="chPol-acc"/>
<binding destination="-2" name="selectedTag" keyPath="options.addChaptersPolicy" id="chPol-tag"/>
<binding destination="-2" name="enabled" keyPath="self.options.addChapters" id="chPol-pen"/>
</connections>
</popUpButton>
<button
toolTip="Create chapter preview images after chapters exist (also enabled automatically when Add chapters is on). Uses the position from General Settings."
verticalHuggingPriority="751"
translatesAutoresizingMaskIntoConstraints="NO"
id="chPrv-btn">
<rect key="frame" x="111" y="260" width="185" height="16" />
<buttonCell
key="cell"
type="check"
title="Create chapter preview images"
bezelStyle="regularSquare"
imagePosition="left"
alignment="left"
controlSize="small"
state="on"
inset="2"
id="chPrv-cell">
<behavior
key="behavior"
changeContents="YES"
doesNotDimImage="YES"
lightByContents="YES" />
<font key="font" metaFont="label" size="11" />
</buttonCell>
<connections>
<binding destination="-2" name="value" keyPath="self.options.chaptersPreview" id="chPrv-bind">
<dictionary key="options">
<bool key="NSValidatesImmediately" value="YES"/>
</dictionary>
</binding>
</connections>
</button>
<popUpButton horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="08Q-2k-0Gb">
<rect key="frame" x="187" y="469" width="70" height="17"/>
<popUpButtonCell key="cell" type="roundRect" title="Poster" bezelStyle="roundedRect" alignment="left" controlSize="small" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" selectedItem="YjX-6f-SPu" id="1t8-o9-ckI">
Expand Down Expand Up @@ -772,7 +893,22 @@
</textField>
</subviews>
<constraints>
<constraint firstItem="0qr-Nt-Pan" firstAttribute="top" secondItem="81E-pQ-vyQ" secondAttribute="bottom" constant="6" symbolic="YES" id="1tu-IW-xOy"/>
<constraint firstItem="chAdd-btn" firstAttribute="top" secondItem="81E-pQ-vyQ" secondAttribute="bottom" constant="6" symbolic="YES" id="1tu-IW-xOy"/>
<constraint firstItem="0qr-Nt-Pan" firstAttribute="top" secondItem="chPol-pop" secondAttribute="bottom" constant="4" id="chRen-top"/>
<constraint firstItem="chAdd-btn" firstAttribute="leading" secondItem="S7L-mA-j3Y" secondAttribute="trailing" constant="7" id="chAdd-lead"/>
<constraint firstItem="chInt-lbl" firstAttribute="leading" secondItem="chAdd-btn" secondAttribute="leading" constant="16" id="chInt-lead"/>
<constraint firstItem="chInt-lbl" firstAttribute="baseline" secondItem="chInt-pop" secondAttribute="baseline" id="chInt-base"/>
<constraint firstItem="chInt-pop" firstAttribute="leading" secondItem="chInt-lbl" secondAttribute="trailing" constant="9" id="chInt-plead"/>
<constraint firstItem="chInt-pop" firstAttribute="top" secondItem="chAdd-btn" secondAttribute="bottom" constant="4" id="chInt-ptop"/>
<constraint firstItem="chPol-lbl" firstAttribute="leading" secondItem="chAdd-btn" secondAttribute="leading" constant="16" id="chPol-lead"/>
<constraint firstItem="chPol-lbl" firstAttribute="baseline" secondItem="chPol-pop" secondAttribute="baseline" id="chPol-base"/>
<constraint firstItem="chPol-pop" firstAttribute="leading" secondItem="chPol-lbl" secondAttribute="trailing" constant="9" id="chPol-plead"/>
<constraint firstItem="chPol-pop" firstAttribute="top" secondItem="chInt-pop" secondAttribute="bottom" constant="4" id="chPol-ptop"/>
<constraint firstItem="chPrv-btn" firstAttribute="top" secondItem="0qr-Nt-Pan" secondAttribute="bottom" constant="6" symbolic="YES" id="chPrv-top"/>
<constraint firstItem="chPrv-btn" firstAttribute="leading" secondItem="S7L-mA-j3Y" secondAttribute="trailing" constant="7" id="chPrv-lead"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="chAdd-btn" secondAttribute="trailing" constant="20" symbolic="YES" id="chAdd-trail"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="chPrv-btn" secondAttribute="trailing" constant="20" symbolic="YES" id="chPrv-trail"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="0qr-Nt-Pan" secondAttribute="trailing" constant="20" symbolic="YES" id="chRen-trail"/>
<constraint firstItem="Uoo-hZ-D1f" firstAttribute="leading" secondItem="UlG-O3-eNc" secondAttribute="leading" constant="20" symbolic="YES" id="1za-LM-bHe"/>
<constraint firstItem="86l-GG-SpO" firstAttribute="firstBaseline" secondItem="vJ5-JB-k1m" secondAttribute="firstBaseline" id="2Fq-90-Fib"/>
<constraint firstItem="eTL-yP-QQ5" firstAttribute="baseline" secondItem="ZVm-yi-3cE" secondAttribute="baseline" id="2QL-tK-mnk"/>
Expand All @@ -799,7 +935,7 @@
<constraint firstItem="FJc-Hc-zCR" firstAttribute="leading" secondItem="LiA-V6-VEx" secondAttribute="leading" constant="16" id="MQ9-dx-nHh"/>
<constraint firstItem="zVi-vU-sh2" firstAttribute="top" secondItem="zEH-aV-Tl7" secondAttribute="bottom" constant="5" id="Mmx-43-nCr"/>
<constraint firstItem="swT-iM-shZ" firstAttribute="baseline" secondItem="ZZk-4j-Ji7" secondAttribute="baseline" id="NVg-BO-A8O"/>
<constraint firstItem="slL-2h-MPl" firstAttribute="top" secondItem="0qr-Nt-Pan" secondAttribute="bottom" constant="6" symbolic="YES" id="Nn2-m3-qrI"/>
<constraint firstItem="slL-2h-MPl" firstAttribute="top" secondItem="chPrv-btn" secondAttribute="bottom" constant="6" symbolic="YES" id="Nn2-m3-qrI"/>
<constraint firstItem="ntU-85-AaJ" firstAttribute="baseline" secondItem="ztL-LO-XhV" secondAttribute="baseline" id="P2e-3H-9uY"/>
<constraint firstItem="s2g-Iz-52P" firstAttribute="leading" secondItem="2Hf-2V-95U" secondAttribute="trailing" constant="9" id="PCQ-pI-3nq"/>
<constraint firstItem="LiA-V6-VEx" firstAttribute="leading" secondItem="S7L-mA-j3Y" secondAttribute="trailing" constant="7" id="PbT-QJ-O9O"/>
Expand Down
12 changes: 1 addition & 11 deletions Classes/DocumentWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,17 +308,7 @@ final class DocumentWindowController: NSWindowController, TracksViewControllerDe
}

@IBAction func addChaptersEvery(_ sender: NSMenuItem) {
let track: MP42ChapterTrack = mp4.chapters ?? { let track = MP42ChapterTrack(); self.mp4.addTrack(track); return track }()
let minutes = sender.tag * 60 * 1000

if minutes > 0 {
for (index, timestamp) in stride(from: 0, to: mp4.duration, by: minutes).enumerated() {
track.addChapter("Chapter \(index + 1)", timestamp: UInt64(timestamp))
}
}
else {
track.addChapter("Chapter 1", timestamp: 0)
}
mp4.addChapters(everyMinutes: sender.tag)

doc.updateChangeCount(.changeDone)
tracksViewController.reloadData()
Expand Down
33 changes: 33 additions & 0 deletions Classes/MP42FileAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,39 @@ private extension String {

extension MP42File {

/// Policy for inserting chapter markers when a chapter track already exists.
enum ChaptersInsertPolicy: Int {
/// Clear existing markers, then insert interval chapters.
case replace = 0
/// Keep existing markers and append interval chapters.
case merge = 1
}

/// Adds chapter markers at a fixed interval, matching the document window "Insert a chapter every…" action.
/// - Parameter minutes: Interval in minutes. Values `<= 0` insert a single chapter at the beginning.
/// - Parameter policy: Whether to replace or merge with existing chapter markers. Defaults to `.merge`.
func addChapters(everyMinutes minutes: Int, policy: ChaptersInsertPolicy = .merge) {
let track: MP42ChapterTrack = chapters ?? {
let track = MP42ChapterTrack()
addTrack(track)
return track
}()

if policy == .replace, track.chapterCount() > 0 {
track.removeChapters(at: IndexSet(integersIn: 0..<Int(track.chapterCount())))
}

let interval = minutes * 60 * 1000

if interval > 0 {
for (index, timestamp) in stride(from: 0, to: duration, by: interval).enumerated() {
track.addChapter("Chapter \(index + 1)", timestamp: UInt64(timestamp))
}
} else {
track.addChapter("Chapter 1", timestamp: 0)
}
}

enum TrackHDType : Int {
case hd720p = 1
case hd1080p = 2
Expand Down
43 changes: 41 additions & 2 deletions Classes/QueueAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,8 @@ class QueuePrettifyAudioTrackNameAction : NSObject, QueueActionProtocol {
class QueueRenameChaptersAction : NSObject, QueueActionProtocol {

var type: QueueActionType { return .pre }
var localizedDescription: String { return NSLocalizedString("Prettifying audio track names", comment: "Action localized description") }
override var description: String { return NSLocalizedString("Prettify audio track names", comment: "Action description") }
var localizedDescription: String { return NSLocalizedString("Renaming chapters titles", comment: "Action localized description") }
override var description: String { return NSLocalizedString("Rename chapters titles", comment: "Action description") }

override init() {}

Expand All @@ -496,6 +496,45 @@ class QueueRenameChaptersAction : NSObject, QueueActionProtocol {

}

/// An action that inserts chapter markers at a fixed interval.
class QueueAddChaptersAction : NSObject, QueueActionProtocol {

var type: QueueActionType { return .pre }
var localizedDescription: String { return NSLocalizedString("Adding chapters", comment: "Action localized description") }
override var description: String { return NSLocalizedString("Add chapters", comment: "Action description") }

let minutes: Int
let policy: MP42File.ChaptersInsertPolicy

init(minutes: Int, policy: MP42File.ChaptersInsertPolicy = .replace) {
self.minutes = minutes
self.policy = policy
}

func runAction(_ item: QueueItem) -> Bool {
guard let mp4 = item.mp4File else { return false }
mp4.addChapters(everyMinutes: minutes, policy: policy)
return true
}

func encode(with aCoder: NSCoder) {
aCoder.encode(Int32(minutes), forKey: "SBQueueAddChaptersActionMinutes")
aCoder.encode(Int32(policy.rawValue), forKey: "SBQueueAddChaptersActionPolicy")
}

required init?(coder aDecoder: NSCoder) {
self.minutes = Int(aDecoder.decodeInt32(forKey: "SBQueueAddChaptersActionMinutes"))
if aDecoder.containsValue(forKey: "SBQueueAddChaptersActionPolicy") {
self.policy = MP42File.ChaptersInsertPolicy(rawValue: Int(aDecoder.decodeInt32(forKey: "SBQueueAddChaptersActionPolicy"))) ?? .replace
} else {
self.policy = .replace
}
}

static var supportsSecureCoding: Bool { return true }

}

enum QueueColorSpaceActionTag: UInt16 {
case None = 1
case Rec601PAL
Expand Down
Loading