A tool to sync music with your favorite devices
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cmd/sync: concurrently transcode and copy single-threaded when handling ipods

previously transcoding was slowing us down

Gee Sawra f459b5d6 fbe16c0e

+107 -79
+107 -79
src/cmd/sync.rs
··· 45 45 /// Treat destination as an iPod. 46 46 /// Tracks will appear as if they were transferred with iTunes or any iPod-compatible software. 47 47 /// Tunz DB will still be created and used to keep track. 48 + /// Transcoding will obey the `--threads' parameter. 48 49 #[arg(long)] 49 50 pub ipod: bool, 50 51 } ··· 64 65 pub async fn run(args: Args) -> Result<()> { 65 66 args.validate()?; 66 67 67 - let threads = match args.ipod { 68 - true => 1, 69 - false => match args.threads { 70 - Some(t) => t, 71 - None => num_cpus::get(), 72 - }, 68 + let threads = match args.threads { 69 + Some(t) => t, 70 + None => num_cpus::get(), 73 71 }; 74 72 75 73 let dest_dir = args.destination.unwrap(); ··· 239 237 )?; 240 238 241 239 if ipod { 242 - // copy single-threaded as iPods update the database when a file is added 243 - // on top of that, all tracks will be re-encoded to AAC 256kbit/s VBR. 244 - // 245 - // we encode in a temp directory, then copy the file over 246 - 247 - let td = tempdir::TempDir::new("tunz_ipod_copy")?; 248 - let td = td.path().to_string_lossy().to_string(); 249 - 250 - let ipod_manager = gpod::Manager::new(dest_dir.clone())?; 251 - 252 - let transcoding = Some(ffmpeg::Quality { 253 - bitrate: ffmpeg::Bitrate::CBR("256k".to_owned()), 254 - sampling_rate: Some(ffmpeg::SamplingRate::Rate441K), 255 - codec: ffmpeg::Codec::AAC, 256 - }); 257 - for track in tracks { 258 - copy(&track, dest_db, &td, transcoding.clone(), link).await?; 259 - let tr_track_path = std::path::PathBuf::from( 260 - &track.storage_path_with_extension(&td, &ffmpeg::Codec::AAC.extension()), 261 - ); 262 - 263 - log::info!("copying to iPod..."); 264 - 265 - ipod_manager.copy( 266 - &track, 267 - tr_track_path, 268 - ffmpeg::Codec::AAC.extension(), 269 - 44100, 270 - 256, 271 - )?; 272 - } 273 - return Ok(()); 240 + return copy_ipod(dest_db, dest_dir, link, threads, tracks).await; 274 241 } 275 242 276 243 stream::iter(tracks) ··· 283 250 .await 284 251 } 285 252 253 + async fn copy_ipod( 254 + dest_db: &db::Instance, 255 + dest_dir: &String, 256 + link: bool, 257 + threads: usize, 258 + tracks: Vec<model::Track>, 259 + ) -> Result<()> { 260 + // copy single-threaded as iPods update the database when a file is added 261 + // on top of that, all tracks will be re-encoded to AAC 256kbit/s VBR. 262 + // 263 + // we encode in a temp directory, then copy the file over 264 + 265 + let td = tempdir::TempDir::new("tunz_ipod_copy")?; 266 + let td = td.path().to_string_lossy().to_string(); 267 + 268 + let ipod_manager = gpod::Manager::new(dest_dir.clone())?; 269 + 270 + let transcoding = Some(ffmpeg::Quality { 271 + bitrate: ffmpeg::Bitrate::VBR("5".to_owned()), 272 + sampling_rate: Some(ffmpeg::SamplingRate::Rate441K), 273 + codec: ffmpeg::Codec::AAC, 274 + }); 275 + 276 + let converted: Vec<_> = stream::iter(tracks) 277 + .map(|track| { 278 + let trs = transcoding.clone(); 279 + let dest_dir = td.clone(); 280 + async move { 281 + copy(&track, dest_db, &dest_dir, trs, link).await?; 282 + 283 + let tr_track_path = std::path::PathBuf::from( 284 + &track.storage_path_with_extension(&dest_dir, &ffmpeg::Codec::AAC.extension()), 285 + ); 286 + 287 + Ok((track.clone(), tr_track_path)) 288 + } 289 + }) 290 + .buffer_unordered(threads) 291 + .try_collect() 292 + .await?; 293 + 294 + for (track, path) in converted { 295 + log::info!("copying {} to iPod...", track); 296 + ipod_manager.copy( 297 + &track, 298 + path.clone(), 299 + ffmpeg::Codec::AAC.extension(), 300 + 44100, 301 + 256, 302 + )?; 303 + std::fs::remove_file(path)?; 304 + } 305 + 306 + return Ok(()); 307 + } 308 + 286 309 async fn dry_run_copy( 287 310 local_db: &db::Instance, 288 311 dest_dir: &String, ··· 401 424 402 425 let opts = CopyOptions::new().overwrite(true); 403 426 404 - let track_for_thread = track.clone(); 405 - let dest_dir_for_thread = dest_dir.clone(); 406 - let ts_for_thread = transcoding_settings.clone(); 407 - 408 - async_std::task::spawn_blocking(move || { 409 - // TODO: don't transcode if it's already lossy 410 - if let Some(ts) = ts_for_thread { 411 - log::info!("copying and transcoding {} ({})", track_for_thread, ts); 427 + async_std::task::spawn_blocking({ 428 + let track_for_thread = track.clone(); 429 + let dest_dir_for_thread = dest_dir.clone(); 430 + let ts_for_thread = transcoding_settings.clone(); 412 431 413 - ffmpeg::convert( 414 - &track_for_thread, 415 - std::path::PathBuf::from_str( 416 - &track_for_thread 417 - .storage_path_with_extension(&dest_dir_for_thread, &ts.codec.extension()), 418 - ) 419 - .unwrap(), 420 - &ts, 421 - )?; 422 - } else { 423 - log::info!("copying {}", track_for_thread); 432 + move || { 433 + if let Some(ts) = ts_for_thread 434 + && track_for_thread.is_lossless() 435 + { 436 + log::info!("transcoding {} ({})", track_for_thread, ts); 424 437 425 - if link { 426 - std::fs::hard_link( 427 - track_for_thread.file_path.clone(), 428 - track_storage_path.clone(), 438 + ffmpeg::convert( 439 + &track_for_thread, 440 + std::path::PathBuf::from_str( 441 + &track_for_thread.storage_path_with_extension( 442 + &dest_dir_for_thread, 443 + &ts.codec.extension(), 444 + ), 445 + ) 446 + .unwrap(), 447 + &ts, 429 448 )?; 430 449 } else { 431 - match copy_with_progress( 432 - track_for_thread.file_path.clone(), 433 - track_storage_path.clone(), 434 - &opts, 435 - |_| {}, 436 - ) { 437 - std::result::Result::Ok(_) => {} 438 - Err(err) => { 439 - return Err(error::Error::CopyError(err)).with_context(|| { 440 - format!( 441 - "Cannot copy {} to {}", 442 - track_for_thread.file_path, track_storage_path 443 - ) 444 - }); 445 - } 446 - }; 450 + log::info!("copying {}", track_for_thread); 451 + 452 + if link { 453 + std::fs::hard_link( 454 + track_for_thread.file_path.clone(), 455 + track_storage_path.clone(), 456 + )?; 457 + } else { 458 + match copy_with_progress( 459 + track_for_thread.file_path.clone(), 460 + track_storage_path.clone(), 461 + &opts, 462 + |_| {}, 463 + ) { 464 + std::result::Result::Ok(_) => {} 465 + Err(err) => { 466 + return Err(error::Error::CopyError(err)).with_context(|| { 467 + format!( 468 + "Cannot copy {} to {}", 469 + track_for_thread.file_path, track_storage_path 470 + ) 471 + }); 472 + } 473 + }; 474 + } 447 475 } 476 + 477 + Ok(()) 448 478 } 449 - 450 - Ok(()) 451 479 }) 452 480 .await?; 453 481