Compare commits

...
3 Commits
7 changed files with 78 additions and 32 deletions
+17 -11
View File
@@ -20,6 +20,9 @@ jobs:
runs-on: macos-15
env:
VERSION: ${{ inputs.version }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
steps:
- uses: actions/checkout@v7
@@ -31,7 +34,7 @@ jobs:
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: |
for variable in APPLE_CERTIFICATE_P12 APPLE_CERTIFICATE_PASSWORD SPARKLE_PRIVATE_KEY; do
for variable in APPLE_CERTIFICATE_P12 APPLE_CERTIFICATE_PASSWORD APPLE_ID APPLE_TEAM_ID APPLE_APP_PASSWORD SPARKLE_PRIVATE_KEY; do
if [[ -z "${!variable:-}" ]]; then
echo "Missing GitHub Actions secret: $variable" >&2
exit 1
@@ -53,7 +56,7 @@ jobs:
fi
echo "SIGNING_IDENTITY=$signing_identity" >> "$GITHUB_ENV"
- name: Build and sign release
- name: Build, sign, and notarize release
timeout-minutes: 30
run: scripts/package_release.sh
@@ -70,13 +73,16 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
tag="v$VERSION"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo "Tag already exists: $tag" >&2
exit 1
if gh release view "$tag" >/dev/null 2>&1; then
gh release upload "$tag" \
"$RUNNER_TEMP/meetingnotes-release/release/Meetingnotes-$VERSION.zip" \
"$RUNNER_TEMP/meetingnotes-release/release/appcast.xml" \
--clobber
else
gh release create "$tag" \
"$RUNNER_TEMP/meetingnotes-release/release/Meetingnotes-$VERSION.zip" \
"$RUNNER_TEMP/meetingnotes-release/release/appcast.xml" \
--target "$GITHUB_SHA" \
--title "Meetingnotes $VERSION" \
--generate-notes
fi
gh release create "$tag" \
"$RUNNER_TEMP/meetingnotes-release/release/Meetingnotes-$VERSION.zip" \
"$RUNNER_TEMP/meetingnotes-release/release/appcast.xml" \
--target "$GITHUB_SHA" \
--title "Meetingnotes $VERSION" \
--generate-notes
+4 -4
View File
@@ -276,7 +276,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 31;
CURRENT_PROJECT_VERSION = 33;
DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\"";
DEVELOPMENT_TEAM = G9LVHZAJNX;
ENABLE_HARDENED_RUNTIME = YES;
@@ -290,7 +290,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.1.19;
MARKETING_VERSION = 1.1.21;
ONLY_ACTIVE_ARCH = NO;
OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI";
PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes;
@@ -312,7 +312,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 31;
CURRENT_PROJECT_VERSION = 33;
DEVELOPMENT_ASSET_PATHS = "\"meetingnotes/Preview Content\"";
DEVELOPMENT_TEAM = G9LVHZAJNX;
ENABLE_HARDENED_RUNTIME = YES;
@@ -326,7 +326,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.1.19;
MARKETING_VERSION = 1.1.21;
ONLY_ACTIVE_ARCH = YES;
OTHER_SWIFT_FLAGS = "$(inherited) -D ENABLE_TCC_SPI";
PRODUCT_BUNDLE_IDENTIFIER = net.jamesbone.meetingnotes;
+7 -4
View File
@@ -209,15 +209,18 @@ final class AudioManager: NSObject, ObservableObject {
private func prepareAudioFiles() throws {
let settings: [String: Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVFormatIDKey: kAudioFormatLinearPCM,
AVSampleRateKey: 16_000,
AVNumberOfChannelsKey: 1,
AVEncoderBitRateKey: 48_000
AVLinearPCMBitDepthKey: 16,
AVLinearPCMIsFloatKey: false,
AVLinearPCMIsBigEndianKey: false,
AVLinearPCMIsNonInterleaved: false
]
let base = FileManager.default.temporaryDirectory
let id = sessionID.uuidString
let micURL = base.appendingPathComponent("meetingnotes-\(id)-mic.m4a")
let systemURL = base.appendingPathComponent("meetingnotes-\(id)-system.m4a")
let micURL = base.appendingPathComponent("meetingnotes-\(id)-mic.wav")
let systemURL = base.appendingPathComponent("meetingnotes-\(id)-system.wav")
let newMicAudioFile = try AVAudioFile(
forWriting: micURL,
settings: settings,
@@ -79,13 +79,7 @@ class UserDefaultsManager {
}
var transcriptionModel: String {
get {
let stored = userDefaults.string(forKey: Keys.transcriptionModel)
if stored == "local-whisper/whisper-large-v3-turbo" {
return "local-parakeet/parakeet-tdt-0.6b-v3"
}
return stored ?? "local-parakeet/parakeet-tdt-0.6b-v3"
}
get { userDefaults.string(forKey: Keys.transcriptionModel) ?? "local-parakeet/parakeet-tdt-0.6b-v3" }
set { userDefaults.set(newValue, forKey: Keys.transcriptionModel) }
}
@@ -275,6 +275,13 @@ final class CoderAPIClient {
let framesPerChunk = preserveSpeakerIdentity
? max(1, input.length)
: AVAudioFramePosition(format.sampleRate * transcriptionChunkDuration)
if fileURL.pathExtension.caseInsensitiveCompare("wav") == .orderedSame,
input.fileFormat.streamDescription.pointee.mFormatID == kAudioFormatLinearPCM,
input.length <= framesPerChunk {
return [AudioChunk(url: fileURL, offset: 0, isTemporary: false)]
}
var chunks: [AudioChunk] = []
var frameOffset: AVAudioFramePosition = 0
+10 -4
View File
@@ -59,10 +59,16 @@ struct SettingsView: View {
}
}
if viewModel.coderModels.first(where: { $0.id == viewModel.settings.transcriptionModel })?.supportsSpeakerDiarization == true {
Label("Remote participants are labeled Speaker 14; your microphone is labeled Me.", systemImage: "person.2.wave.2")
.font(.caption)
.foregroundColor(.secondary)
if let selectedModel = viewModel.coderModels.first(where: { $0.id == viewModel.settings.transcriptionModel }) {
if selectedModel.supportsSpeakerDiarization {
Label("Remote participants are labeled Speaker 14; your microphone is labeled Me.", systemImage: "person.2.wave.2")
.font(.caption)
.foregroundColor(.secondary)
} else {
Label("Remote participants are labeled Them; your microphone is labeled Me.", systemImage: "person.2")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
+32 -2
View File
@@ -14,6 +14,9 @@ APP_PATH="$DERIVED_DATA/Build/Products/Release/$APP_NAME.app"
required_variables=(
VERSION
SIGNING_IDENTITY
APPLE_ID
APPLE_TEAM_ID
APPLE_APP_PASSWORD
SPARKLE_PRIVATE_KEY
GITHUB_REPOSITORY
)
@@ -80,6 +83,33 @@ ARCHIVE_NAME="$APP_NAME-$VERSION.zip"
ARCHIVE_PATH="$RELEASE_DIR/$ARCHIVE_NAME"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ARCHIVE_PATH"
NOTARY_RESULT="$BUILD_ROOT/notary-result.json"
xcrun notarytool submit "$ARCHIVE_PATH" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_PASSWORD" \
--wait \
--timeout 20m \
--output-format json > "$NOTARY_RESULT"
NOTARY_STATUS=$(plutil -extract status raw -o - "$NOTARY_RESULT")
if [[ "$NOTARY_STATUS" != "Accepted" ]]; then
submission_id=$(plutil -extract id raw -o - "$NOTARY_RESULT")
xcrun notarytool log "$submission_id" \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_PASSWORD" || true
echo "Apple notarization failed with status: $NOTARY_STATUS" >&2
exit 1
fi
xcrun stapler staple "$APP_PATH"
xcrun stapler validate "$APP_PATH"
spctl --assess --type execute --verbose=2 "$APP_PATH"
rm -f "$ARCHIVE_PATH"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" "$ARCHIVE_PATH"
GENERATE_APPCAST=$(find "$DERIVED_DATA/SourcePackages/artifacts" -type f -name generate_appcast -print -quit)
if [[ -z "$GENERATE_APPCAST" ]]; then
echo "Sparkle generate_appcast tool was not found" >&2
@@ -97,8 +127,8 @@ grep -q "$DOWNLOAD_URL$ARCHIVE_NAME" "$RELEASE_DIR/appcast.xml"
grep -q 'sparkle:edSignature=' "$RELEASE_DIR/appcast.xml"
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
printf 'Built and Developer ID-signed Meetingnotes %s. The GitHub release is ready to publish.\n' \
printf 'Built, Developer ID-signed, notarized, and stapled Meetingnotes %s. The GitHub release is ready to publish.\n' \
"$VERSION" >> "$GITHUB_STEP_SUMMARY"
fi
echo "Signed release artifacts are ready in $RELEASE_DIR"
echo "Signed and notarized release artifacts are ready in $RELEASE_DIR"