Skip to content

Commit dafab96

Browse files
wip
1 parent d762793 commit dafab96

2 files changed

Lines changed: 125 additions & 37 deletions

File tree

apps/desktop/src-tauri/src/lib.rs

Lines changed: 98 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,11 +1769,17 @@ async fn import_and_upload_video(
17691769
source_path: PathBuf,
17701770
channel: Channel<UploadProgress>,
17711771
) -> Result<UploadResult, String> {
1772-
// 1) Auth/plan checks (reuse code from upload_exported_video)
1773-
let Ok(Some(auth)) = AuthStore::get(&app) else {
1774-
AuthStore::set(&app, None).map_err(|e| e.to_string())?;
1775-
return Ok(UploadResult::NotAuthenticated);
1776-
};
1772+
// Importing always requires Pro
1773+
{
1774+
let Ok(Some(auth)) = AuthStore::get(&app) else {
1775+
AuthStore::set(&app, None).map_err(|e| e.to_string())?;
1776+
return Ok(UploadResult::NotAuthenticated);
1777+
};
1778+
1779+
if !auth.is_upgraded() {
1780+
return Ok(UploadResult::UpgradeRequired);
1781+
}
1782+
}
17771783

17781784
// Validate source file exists and is a valid video
17791785
if !source_path.exists() {
@@ -1788,10 +1794,6 @@ async fn import_and_upload_video(
17881794
let source_metadata = build_video_meta(&source_path)
17891795
.map_err(|err| format!("Error getting source video meta: {err}"))?;
17901796

1791-
if !auth.is_upgraded() && source_metadata.duration_in_secs > 300.0 {
1792-
return Ok(UploadResult::UpgradeRequired);
1793-
}
1794-
17951797
// 2) Create new project dir
17961798
let id = uuid::Uuid::new_v4().to_string();
17971799
let recording_dir = recordings_path(&app).join(format!("{id}.cap"));
@@ -1930,6 +1932,7 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
19301932
.ok_or_else(|| "No video stream found in input file".to_string())?;
19311933

19321934
let video_stream_index = video_stream.index();
1935+
let input_time_base = video_stream.time_base();
19331936

19341937
let mut octx = ffmpeg::format::output(&output)
19351938
.map_err(|e| format!("Failed to create output context: {e}"))?;
@@ -1942,6 +1945,7 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
19421945
.format()
19431946
.flags()
19441947
.contains(ffmpeg::format::flag::Flags::GLOBAL_HEADER);
1948+
19451949
let mut encoder = ffmpeg::codec::context::Context::new_with_codec(codec)
19461950
.encoder()
19471951
.video()
@@ -1954,12 +1958,26 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
19541958
.video()
19551959
.map_err(|e| format!("Failed to create video decoder: {e}"))?;
19561960

1957-
// Configure encoder
1961+
// Configure encoder with proper settings
19581962
encoder.set_width(decoder.width());
19591963
encoder.set_height(decoder.height());
19601964
encoder.set_format(ffmpeg::format::Pixel::YUV420P);
1961-
encoder.set_frame_rate(Some(ffmpeg::Rational::from((30, 1)))); // 30fps as per spec
1962-
encoder.set_time_base(ffmpeg::Rational::from((1, 30)));
1965+
1966+
// Use input stream frame rate or fallback to 30fps
1967+
let fps = {
1968+
let stream_fps = video_stream.avg_frame_rate();
1969+
if stream_fps.numerator() > 0 && stream_fps.denominator() > 0 {
1970+
stream_fps
1971+
} else {
1972+
ffmpeg::Rational::from((30, 1))
1973+
}
1974+
};
1975+
encoder.set_frame_rate(Some(fps));
1976+
encoder.set_time_base(fps.invert());
1977+
1978+
// Set quality parameters
1979+
encoder.set_bit_rate(decoder.bit_rate().max(1_000_000)); // At least 1Mbps
1980+
encoder.set_max_b_frames(0); // Disable B-frames for compatibility
19631981

19641982
if global_header {
19651983
encoder.set_flags(ffmpeg::codec::flag::Flags::GLOBAL_HEADER);
@@ -1975,8 +1993,9 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
19751993

19761994
output_stream.set_parameters(&encoder);
19771995
let output_stream_index = output_stream.index();
1978-
let output_stream_time_base = output_stream.time_base();
1996+
let output_time_base = output_stream.time_base();
19791997

1998+
// Write header before processing
19801999
octx.write_header()
19812000
.map_err(|e| format!("Failed to write header: {e}"))?;
19822001

@@ -1991,12 +2010,16 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
19912010
)
19922011
.map_err(|e| format!("Failed to create scaler: {e}"))?;
19932012

1994-
let mut frame_index = 0;
19952013
let mut decoded_frame = ffmpeg::frame::Video::empty();
19962014
let mut encoded_frame = ffmpeg::frame::Video::empty();
2015+
let mut output_frame_count = 0i64;
19972016

1998-
for (stream, packet) in ictx.packets() {
2017+
// Process packets
2018+
for (stream, mut packet) in ictx.packets() {
19992019
if stream.index() == video_stream_index {
2020+
// Rescale packet timestamp to decoder time base
2021+
packet.rescale_ts(input_time_base, decoder.time_base());
2022+
20002023
decoder
20012024
.send_packet(&packet)
20022025
.map_err(|e| format!("Failed to send packet to decoder: {e}"))?;
@@ -2006,17 +2029,30 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
20062029
.run(&decoded_frame, &mut encoded_frame)
20072030
.map_err(|e| format!("Failed to scale frame: {e}"))?;
20082031

2009-
encoded_frame.set_pts(Some(frame_index));
2010-
frame_index += 1;
2032+
// Set proper PTS for encoded frame
2033+
encoded_frame.set_pts(Some(output_frame_count));
2034+
output_frame_count += 1;
20112035

20122036
encoder
20132037
.send_frame(&encoded_frame)
20142038
.map_err(|e| format!("Failed to send frame to encoder: {e}"))?;
20152039

2040+
// Flush encoder packets
20162041
let mut encoded_packet = ffmpeg::packet::Packet::empty();
20172042
while encoder.receive_packet(&mut encoded_packet).is_ok() {
20182043
encoded_packet.set_stream(output_stream_index);
2019-
encoded_packet.rescale_ts(encoder.time_base(), output_stream_time_base);
2044+
2045+
// Properly rescale timestamps
2046+
encoded_packet.rescale_ts(encoder.time_base(), output_time_base);
2047+
2048+
// Ensure valid timestamps
2049+
if encoded_packet.pts().is_none() {
2050+
encoded_packet.set_pts(Some(0));
2051+
}
2052+
if encoded_packet.dts().is_none() {
2053+
encoded_packet.set_dts(encoded_packet.pts());
2054+
}
2055+
20202056
encoded_packet
20212057
.write_interleaved(&mut octx)
20222058
.map_err(|e| format!("Failed to write packet: {e}"))?;
@@ -2025,6 +2061,41 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
20252061
}
20262062
}
20272063

2064+
// Flush decoder
2065+
decoder
2066+
.send_eof()
2067+
.map_err(|e| format!("Failed to flush decoder: {e}"))?;
2068+
2069+
while decoder.receive_frame(&mut decoded_frame).is_ok() {
2070+
scaler
2071+
.run(&decoded_frame, &mut encoded_frame)
2072+
.map_err(|e| format!("Failed to scale frame: {e}"))?;
2073+
2074+
encoded_frame.set_pts(Some(output_frame_count));
2075+
output_frame_count += 1;
2076+
2077+
encoder
2078+
.send_frame(&encoded_frame)
2079+
.map_err(|e| format!("Failed to send frame to encoder: {e}"))?;
2080+
2081+
let mut encoded_packet = ffmpeg::packet::Packet::empty();
2082+
while encoder.receive_packet(&mut encoded_packet).is_ok() {
2083+
encoded_packet.set_stream(output_stream_index);
2084+
encoded_packet.rescale_ts(encoder.time_base(), output_time_base);
2085+
2086+
if encoded_packet.pts().is_none() {
2087+
encoded_packet.set_pts(Some(0));
2088+
}
2089+
if encoded_packet.dts().is_none() {
2090+
encoded_packet.set_dts(encoded_packet.pts());
2091+
}
2092+
2093+
encoded_packet
2094+
.write_interleaved(&mut octx)
2095+
.map_err(|e| format!("Failed to write packet: {e}"))?;
2096+
}
2097+
}
2098+
20282099
// Flush encoder
20292100
encoder
20302101
.send_eof()
@@ -2033,7 +2104,15 @@ async fn transcode_to_mp4(input: &Path, output: &Path) -> Result<(), String> {
20332104
let mut encoded_packet = ffmpeg::packet::Packet::empty();
20342105
while encoder.receive_packet(&mut encoded_packet).is_ok() {
20352106
encoded_packet.set_stream(output_stream_index);
2036-
encoded_packet.rescale_ts(encoder.time_base(), output_stream_time_base);
2107+
encoded_packet.rescale_ts(encoder.time_base(), output_time_base);
2108+
2109+
if encoded_packet.pts().is_none() {
2110+
encoded_packet.set_pts(Some(0));
2111+
}
2112+
if encoded_packet.dts().is_none() {
2113+
encoded_packet.set_dts(encoded_packet.pts());
2114+
}
2115+
20372116
encoded_packet
20382117
.write_interleaved(&mut octx)
20392118
.map_err(|e| format!("Failed to write final packet: {e}"))?;

apps/desktop/src/routes/(window-chrome)/settings/recordings.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
Show,
2222
} from "solid-js";
2323
import { createStore, produce } from "solid-js/store";
24+
import toast from "solid-toast";
2425
import CapTooltip from "~/components/Tooltip";
2526
import { trackEvent } from "~/utils/analytics";
2627
import { createTauriEventListener } from "~/utils/createEventListener";
@@ -134,26 +135,34 @@ export default function Recordings() {
134135
});
135136
};
136137

137-
const handleImportUpload = async () => {
138-
try {
139-
const file = await open({
140-
multiple: false,
141-
filters: [
142-
{
143-
name: "Video",
144-
extensions: ["mp4", "mov", "mkv", "webm", "avi"],
145-
},
146-
],
147-
});
148-
149-
if (!file || Array.isArray(file)) return;
150-
151-
trackEvent("import_upload_started");
152-
153-
const channel = new Channel<UploadProgress>(() => {});
138+
const importVideo = createMutation(() => ({
139+
mutationKey: ["importVideo"],
140+
mutationFn: async (path: string) => {
141+
await commands.importAndUploadVideo(
142+
path,
143+
new Channel<UploadProgress>(() => {}),
144+
);
145+
},
146+
onSuccess: () => {
147+
recordings.refetch();
148+
},
149+
onError: (error: Error) => toast.error("Failed to import video:", error),
150+
}));
154151

155-
await commands.importAndUploadVideo(file, channel);
152+
const handleImportUpload = async () => {
153+
const path = await open({
154+
multiple: false,
155+
filters: [
156+
{
157+
name: "Video",
158+
extensions: ["mp4", "mov", "mkv", "webm", "avi"],
159+
},
160+
],
161+
});
162+
if (!path) return;
163+
importVideo.mutate(path);
156164

165+
try {
157166
// Refetch recordings to show the new imported recording
158167
recordings.refetch();
159168

0 commit comments

Comments
 (0)