/// <summary> /// /// </summary> /// <param name="video"></param> /// <param name="audio"></param> /// <param name="syncPoints">A collection of points in the video that will be synced with specified points in the audio. These must be in ascending order, both for the video positions and audio positions. </param> /// <param name="projectName"></param> /// <param name="version"></param> /// <param name="themeID"></param> /// <param name="templateID"></param> /// <returns></returns> public static WmmProject Sync( FileInfo video, FileInfo audio, double videoLength, double audioLength, SyncPointCollection syncPoints, //Use SyncPointCollection not a collection of SyncedRanges to avoid having to validate as much stuff string projectName = DefaultProjectName, int version = WmmProject.DefaultVersion, int themeID = WmmProject.DefaultThemeID, string templateID = WmmProject.DefaultTemplateID ) { if (video == null) { throw new ArgumentNullException(nameof(video)); } if (audio == null) { throw new ArgumentNullException(nameof(audio)); } if (videoLength < 0) { throw new ArgumentException("Cannot be negative.", nameof(videoLength)); } if (audioLength < 0) { throw new ArgumentException("Cannot be negative.", nameof(audioLength)); } if (syncPoints == null) { throw new ArgumentNullException(nameof(syncPoints)); } projectName = string.IsNullOrEmpty(projectName) ? DefaultProjectName : projectName; templateID = string.IsNullOrEmpty(templateID) ? WmmProject.DefaultTemplateID : templateID; if (syncPoints.First.AudioPos > audioLength) { throw new InvalidOperationException( $"The {nameof(SyncPoint.AudioPos)} of the first {nameof(SyncPoint)} is '{syncPoints.First.AudioPos}', " + $"which is greater than the {nameof(audioLength)} ('{audioLength}')." ); } if (syncPoints.First.VideoPos > videoLength) { throw new InvalidOperationException( $"The {nameof(SyncPoint.VideoPos)} of the first {nameof(SyncPoint)} is '{syncPoints.First.VideoPos}', " + $"which is greater than the {nameof(videoLength)} ('{videoLength}')." ); } var extendIDs = new ExtentIdGenerator(); // //Section of audio skipped at the start, before the first sync point. // //I don't think windows movie maker allows durations with no video, // //so the video and audio both need to start at 0h:00m:00s, however there may // //be some of either skipped at the start. As the audio time is used as // //the absolute time, while the video is stretched and squished, it // //makes sense to let this correspond to the duration of audio // //before the first sync point. // //This is subtracted from a bunch of calculations. // var initialAudioSkipped = syncPoints.First.AudioPos; return(WmmProject.Create( projectName: projectName, mediaItems: new[] { new WmmMediaItem( filePath: video.FullName, mediaItemID: VideoMediaItemID, mediaType: WmmMediaType.Video ), new WmmMediaItem( filePath: audio.FullName, mediaItemID: AudioMediaItemID, mediaType: WmmMediaType.Audio ), }, videoClips: ( syncPoints .GetSyncedRanges() .SelectMany( r => new WmmVideoClip[] { new WmmVideoClip( //TODO: Currently idk if the video order is actually specified extentID: extendIDs.GetNextID(), mediaItemID: VideoMediaItemID, inTime: r.Start.VideoPos, outTime: r.End.VideoPos, speed: r.SyncedVidSpeed ), } ) ), audioClips: new WmmAudioClip[] { new WmmAudioClip( //TODO: Currently the time at which the audio should actually play isn't specified?? extentID: extendIDs.GetNextID(), mediaItemID: AudioMediaItemID, inTime: syncPoints.First.AudioPos, outTime: Math.Min(syncPoints.Last.AudioPos, audioLength), speed: 1 ) }, version: version, themeID: themeID, templateID: templateID )); }
static void SyncAudioVideo() { const double firstBeatVideoPos = 16.03; //13.70; const double firstBeatAudioPos = 37.365; //TODO: ^ think this should actually be 37.675 const double beatInterval = ( //1st manual counting attempt: // 0.26077241379310344827586206896552 //(75.177 - 37.365)/145 (my own measurements & counting - I think I miscounted or something) //Online lookup: // 128 BPM. // Results in interval of 0.46875s (exactly) // Based on: // - https://www.audiokeychain.com/track/tgi/laidback-luke-peking-duk-mufasa // - https://tunebat.com/Info/Mufasa-Original-Mix-Laidback-Luke-Peking-Duck/49mTyByYvtaQwedg3VM6ky // - https://www.beatport.com/track/mufasa-original-mix/6319471 //Due to possible innacuraccy in the results, I then did some more manual measurements: //2nd manual counting attempt: // 76 beats in 44.918 seconds = 101.5183222761476 bpm = interval of 0.59102631578947s //Completely wrong, it was meant to be 96 beats //3rd manual counting attempt: // 96 beats in 44.914 seconds (37.673...82.588) = 128.24509061762479405085274079352 bpm = interval of 0.46785416666666666666666666666665s //1st attempt checking the bpm of the build up of the song: // 80 beats in 37.495 seconds (00.178...37.673) = 128.0170689425256700893452460328 bpm // .'. should be safe to assume bpm is constant throughout song (unless results differ significantly) //4th manual counting attempt: // 212 beats in 99.444 seconds (82.588...182.032) = 127.91118619524556534330879691082 bpm = interval of 0.46907547169811320754716981132077s //5th manual counting attempt: // 212 + 96 + 80 = 388 beats in 181.854 seconds (00.178...182.032) = 128.01478108812563924906793361708 bpm = interval of 0.46869587628865979381443298969071 0.46869587628865979381443298969071 //5th manual count ); const int barLength = 4; var beats = new BeatPositioner( firstBeat: firstBeatAudioPos, interval: beatInterval, barLength: barLength ); //Intro music: // https://open.spotify.com/track/1oDAFTOXZGSQedBa6hXGhT?si=jIQijymKSayXfa-l_erSkw (sugar plum v1) // 00:27 to 00:43 // 01:37 to 01:55 // https://open.spotify.com/track/7a9g6fPCrxdZ8p4tLCWNdR?si=GrxBoHLVT5mNm7jjYcNEDQ (sugar plum v2) // 00:30 to 00:48 // 01:44 to 02:01 // https://www.youtube.com/watch?v=45lNvNsdPLc (sugar plum v3) // 00:26 to 00:41 (use this one) (15 seconds) // 01:30 to 01:45 WmmProject project = VideoMusicSync.Sync( video: new FileInfo(@"C:\Users\Alex\Pictures\From Phone\Pictures\Phone\Personal\Mum Sleeping TV\Z1_VID_20180425_205156-face-1-blurred-by-youtube-via-clipconverter.mp4"), audio: new FileInfo(@"C:\Users\Alex\Pictures\Meme Templates\Meme Making\Mum Headbanging\Peking Duk & Laidback Luke - Mufasa (Radio Edit) - edit 4.wav"), videoLength: 344.91, audioLength: 183.948, syncPoints: ( new SyncPointCollection( new BeatSyncSequence( firstSyncPoint: new SyncPoint(firstBeatVideoPos, firstBeatAudioPos), beatInterval: beatInterval, barLength: barLength ) .SkipByBeats(2.0 / 3.0).SyncWithVideo(16.49) //creep forward .SkipToBeat(Beat.NextBeat).SyncWithVideo(17.83) //slam forward .SkipByBeats(2.0 / 3.0).SyncWithVideo(19.30) //sideways nod .SkipToBeat(Beat.NextBeat).SyncWithVideo(21.20) //slam back .SkipByBeats(2.0 / 3.0).SyncWithVideo(22.23) //slow small nod forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(23.17) //small slam back .SkipByBeats(2.0 / 3.0).SyncWithVideo(24.03) //creep forward .SkipToBeat(Beat.NextBeat).SyncWithVideo(24.97) //slam forward .SkipByBeats(1.0 / 3.0).SyncWithVideo(27.30) //creep back & sideways .SkipByBeats(1.0 / 3.0).SyncWithVideo(28.83) //slam back on inserted beat .SkipToBeat(Beat.NextBeat) .SkipByBeats(1.0 / 3.0).SyncWithVideo(29.00) //creep forwards until inserted beat .SkipToBeat(Beat.NextBeat).SyncWithVideo(30.63) //slam forwards from inserted beat to actual beat .SkipByBeats(2.0 / 3.0).SyncWithVideo(33.20) //slow sideways nod .SkipToBeat(Beat.NextBeat).SyncWithVideo(35.27) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(35.70) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(36.73) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(37.80) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(40.43) //slam backwards //40.83 .SkipByBeats(2.0 / 3.0).SyncWithVideo(41.20) //stay still then creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(42.30) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(43.47) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(44.33) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(45.13) //hover .SkipToBeat(Beat.NextBeat).SyncWithVideo(45.97) //slam further backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(46.27) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(46.90) //slam forwards //Normal beat, replaced by synced to notes version //.SkipByBeats(2.0/3.0 ).SyncWithVideo( 49.37) //hover & creep backwards //.SkipToBeat (Beat.NextBeat).SyncWithVideo( 50.90) //slam backwards //.SkipByBeats(2.0/3.0 ).SyncWithVideo( 51.27) //creep forwards //.SkipToBeat (Beat.NextBeat).SyncWithVideo( 52.17) //slam forwards //.SkipByBeats(2.0/3.0 ).SyncWithVideo( 53.67) //sideways nod //.SkipToBeat (Beat.NextBeat).SyncWithVideo( 54.97) //slam backwards //No idea but this didn't sync up properly with the beats. The next bit worked instead //.SkipToBeat (Beat.NextBeat) //.SkipByBeats(1.0/3.0 ).SyncWithVideo( 47.23) //almost freeze - extended //49.47 //.SkipByBeats(1.0/3.0 ).SyncWithVideo( 50.90) //slam backwards on note //.SkipToBeat (Beat.NextBeat).SyncWithVideo( 52.17) //slam forwards on beat //.SkipByBeats(1.0/3.0 ).SyncWithVideo( 53.67) //sideways nod //.SkipByBeats(1.0/3.0 ).SyncWithVideo( 54.97) //slam backwards on note //.SkipToBeat (Beat.NextBeat).SyncWithVideo( 56.07) //slam forwards on beat //I have no idea why this bit works but it does .SkipToBeat(Beat.NextBeat).SyncWithVideo(47.23) //almost freeze - extended .SkipByBeats(1.0 / 3.0).SyncWithVideo(50.90) //slam backwards on note .SkipByBeats(1.0 / 3.0).SyncWithVideo(52.17) //slam forwards on beat .SkipToBeat(Beat.NextBeat).SyncWithVideo(53.67) //sideways nod .SkipByBeats(1.0 / 3.0).SyncWithVideo(54.97) //slam backwards on note .SkipByBeats(1.0 / 3.0).SyncWithVideo(56.07) //slam forwards on beat .SkipToBeat(Beat.NextBeat).SyncWithVideo(56.83) //creep backwards .SkipByBeats(1.0 / 3.0).SyncWithVideo(58.80) //slam backwards on note .SkipByBeats(1.0 / 3.0).SyncWithVideo(61.17) //slam forwards on beat //Back to normal from here, makes sense again .SkipByBeats(2.5 / 3.0).SyncWithVideo(62.00) //creep backwards //This one looked awkward as two slams back in a row, so I made it one stuttered //slam (as there's also a wobble, which adds a third pause) //.SkipByBeats(2.5/3.0 ).SyncWithVideo( 64.40) //wobble & creep backwards //.SkipToBeat (Beat.NextBeat).SyncWithVideo( 65.63) //mini slam backwards pre-beat //.SkipByBeats(2.5/3.0 ).SyncWithVideo( 66.73) //hover .SkipToBeat(Beat.NextBeat).SyncWithVideo(67.27) //stuttered slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(67.50) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(68.33) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(69.00) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(69.70) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(70.97) //hover .SkipToBeat(Beat.NextBeat).SyncWithVideo(72.27) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(72.63) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(73.73) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(78.87) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(80.10) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(80.87) //hover .SkipToBeat(Beat.NextBeat).SyncWithVideo(81.67) //slam further backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(82.13) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(83.27) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(83.90) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(86.37) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(87.37) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(88.47) //slam forwards .SkipToBeat(Beat.NextBeat) .SkipToBeat(Beat.NextBeat) .SkipToBeat(Beat.NextBeat) .SkipToBeat(Beat.NextBeat) .SkipToBeat(Beat.NextBeat) //5 total .SkipByBeats(2.0 / 3.0).SyncWithVideo(94.57) //intermission (half wake up) //.SkipByBeats(2.0/3.0 ).SyncWithVideo( 98.93) //intermission .SkipToBeat(Beat.NextBeat).SyncWithVideo(100.33) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(100.67) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(101.70) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(102.03) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(103.23) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(103.53) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(104.67) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(105.27) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(106.00) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(108.13) //hover & creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(109.23) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(111.43) //side glance & creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(112.97) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(113.33) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(114.33) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(115.13) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(116.10) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(120.73) //hover & creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(121.83) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(127.93) //hover & creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(129.83) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(130.37) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(131.07) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(131.80) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(135.03) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(135.23) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(136.20) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(137.50) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(138.80) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(139.30) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(140.07) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(141.43) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(142.10) //slam backwards (small) .SkipByBeats(2.0 / 3.0).SyncWithVideo(142.63) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(143.33) //slam forwards (small) //.SkipByBeats(5) //.SkipToBeat (Beat.NextBeat).SyncWithVideo(152.83) //intermission .SkipByBeats(2.0 / 3.0).SyncWithVideo(152.83) //long hover & creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(154.60) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(154.97) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(155.70) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(156.20) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(158.13) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(158.50) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(159.43) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(160.77) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(162.40) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(165.00) //hover & creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(166.20) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(167.73) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(170.03) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(170.50) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(171.50) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(172.70) //creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(173.33) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(173.90) //hover .SkipToBeat(Beat.NextBeat).SyncWithVideo(175.20) //slam further backwards //174.97 .SkipByBeats(2.0 / 3.0).SyncWithVideo(175.63) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(176.87) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(179.40) //creep backwards (half wake up) .SkipToBeat(Beat.NextBeat).SyncWithVideo(181.50) //slam backwards (with one stutter) .SkipByBeats(2.0 / 3.0).SyncWithVideo(182.87) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(183.87) //slam forwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(187.00) //hover & creep backwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(190.03) //slam backwards .SkipByBeats(2.0 / 3.0).SyncWithVideo(190.60) //creep forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(191.77) //slam forwards .SkipToBeat(Beat.NextBeat).SyncWithVideo(193.20) //end video between last beat and here // .SkipByBeats(2.0/3.0 ).SyncWithVideo(193.20) //sideways nod // .SkipToBeat (Beat.NextBeat).SyncWithVideo(195.77) //slam backwards (with one stutter) // .SkipByBeats(2.0/3.0 ).SyncWithVideo(196.00) //creep forwards // .SkipToBeat (Beat.NextBeat).SyncWithVideo(196.80) //slam forwards // .SkipByBeats(10.0).SyncWithVideo(196.80 + 5) //wake up // .SkipByBeats(2.0/3.0 ).SyncWithVideo() // .SkipToBeat (Beat.NextBeat).SyncWithVideo() .SyncPoints.ToImmutableList() ) ) ); project.ToXml().Save( @"C:\Users\Alex\Pictures\Meme Templates\Meme Making\Mum Headbanging\" + "Sync " + DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + ".wlmp" ); }