Here's my five latest blog posts - or you can browse a complete archive of all my posts since 2008.

Using the Contour ShuttleXpress with Camtasia

The Contour ShuttleXpress is a controller designed for editing audio and video. I use it all the time in applications like Logic Pro X; the jog dial and shuttle wheel are brilliant for, well, jogging and shuttling around your project’s timeline.

contour-shuttlexpress-camtasia-settings

Camtasia is a screen recording and video editing app from the folks at TechSmith, which I use for doing the screen recordings I use in my presentations and workshops.

Out of the box, the ShuttleXpress doesn’t support Camtasia, so I created an application profile for it.

Action Keystroke What it does
Jog Left , Step backwards in timeline
Jog Right . Step forwards in timeline
Shuttle left/right , . Move forwards backwards in timeline. Each position on the shuttle is independently programmable, so “shuttle in left 7” sends , as fast as possible, “shuttle in left 6” sends 60 strokes per second, and so on. It’s not a perfect timeline shuttle but it’s close enough.
Button 1 Ctrl + Home Jump to beginning of timeline
Button 2 Ctrl+Alt+, Move playhead to previous clip
Button 3 Shift+Ctrl+Alt+Left Arrow Extend selection to previous clip
Button 4 Ctrl+Alt+. Move playhead to next clip
Button 5 Shift+Ctrl+S Split all tracks at playhead

The workflow here is:

  1. Shuttle until you find the start of the mistake
  2. Button 5 to split all tracks
  3. Shuttle forward to the end of the mistake
  4. Button 5 again to split all tracks
  5. Button 3 to select the new region with the mistake in it
  6. Delete to remove the region.

and if you switch on “magnet mode” for the track you’re editing, when you delete the mistake it’ll automatically snap the remaining pieces together so it doesn’t leave a gap.

You can download the settings file here:

CamtasiaStudio-ShuttleXpress.pref

and import it using Settings Management > Options > Import Settings in the Contour Shuttle Device Configuration app:

image-20240317193728441

Happy shuttling :)

"Using the Contour ShuttleXpress with Camtasia" was posted by Dylan Beattie on 17 March 2024 • permalink

Recording Meetups

For the last few months, I’ve been recording the talks at the London .NET User Group on video so we can post them on YouTube after the event. The videos are online at https://www.youtube.com/@LondonDotNet, and I’ve had some questions about the setup I use to record them. Some from interested people looking to do something similar at their own events, some from people who show up early while I’m still setting up and go “wow, what’s with all the shiny things?” So here’s how it works.

dylanbeattie-recording-meetups

(I made a diagram! Want it as a PDF? Here you go: dylanbeattie-recording-meetups.pdf.)

Short answer:

  • Canon EOS M200 camera
  • Elgato Camlink
  • Elgato Game Capture HD60S+
  • AnkerWork 650 wireless microphones
  • OBS Studio on an M1 Macbook Pro
  • Lots of HDMI and USB cables

For the long answer: the key here is minimal post-production. When it comes to editing two hours of video footage, “tomorrow” turns into “later” turns into “never” with astonishing regularity. Other than editing out any major technical problems, I want to leave the meetup with a video file that’s ready to upload to YouTube.

I also don’t want presenters to have to do any more than plug in HDMI, clip on a microphone, and go. I’ve done a few events where I’ve had to install drivers or sharing software for their “wireless smart display” system: I really don’t like doing that, and I’m not going to ask our presenters to do it either.

In terms of portability, I assume the venue will provide HDMI for the presenter’s slides, and there’s a power socket available to plug everything in. Everything else needs to fit in a backpack and set up in less than fifteen minutes.

The camera I use is a Canon EOS M200. It’s a mirrorless SLR with clean HDMI out: what this means in practice is you can plug it into an Elgato Camlink, plug that into your laptop, and it shows up as a webcam - albeit a really, really good one. The camera runs off a USB power supply so I don’t have to worry about running out of battery halfway through a recording.

Bad news: Canon has apparently discontinued the M200, along with the rest of the M series, in favour of the Canon EOS R series. This makes me sad, because the M200 is a wonderful camera.

Good news: Elgato maintain a list of supported cameras, so any of the cameras on this list will do the trick:

https://www.elgato.com/uk/en/s/cam-link-camera-check

If I can, I’ll bring a proper camera tripod along. If not, I’ll bring a SmallRig “magic arm” clamp and find a convenient chair, lamp, window frame or something to clamp it to.

Slides: Elgato Game Capture HD60S+

To capture the speaker’s laptop, I use an Elgato Game Capture HD60S+ (now superseded by the Game Capture HD60 X) This works like a Camlink - turns HDMI into a USB video signal - but it has an HDMI output that uses passthrough, so I can plug it in between the speaker’s laptop and the venue’s projector/screen and then tap a USB feed off the side into my laptop.

Audio: Ankerwork M650 Wireless Microphones

The first time I tried this, I used a RØDE shotgun microphone attached to the camera, but the audio quality wasn’t great, so I invested in a set of AnkerWork M650 wireless microphones. These are wonderful and amazing and excellent and I can’t say enough good things about them, but I’m a big fan of Anker gear, and folks I know who use the RØDE Wireless ME or the DJI MIC system say the same things about their setup.

img

Recording: Macbook Pro + OBS Studio

I run all these devices into an M1 Macbook Pro, and then record everything using OBS Studio.

Set OBS to record MKV format, and enable “automatically remux to MP4” in Settings > Advanced.

If you record direct to MP4, a system crash will leave you with an unusable video file, but MKV files aren’t as widely supported, so recording to MKV and then automatically remuxing to MP4 gives you the best of both worlds.

2024-02-20_11-39-52 (1)

This is where the “minimal post-production” bit kicks in. I’ve got four scenes set up in OBS:

Camera Only:

Slides + Camera:

2024-02-20_11-09-02

Camera + Slides:

2024-02-20_11-09-11

Slides Only:

2024-02-20_11-09-18

Plus, before the meetup I’ll make a title slide for each speaker and create a scene with that title slide plus the microphone, which means you can record audio over the title - great for introductions and preamble:

2024-02-20_11-09-22

Note that “Slides + Camera” has the slides on the left, and “Camera + Slides” is the same thing, but with the slides on the right. Composition works better if the speaker is facing towards the middle of the frame, so which one of these gets used depends on where the camera is in relation to the speaker on the night.

OK, showtime. Plug everything in, get it working, probably reboot a couple of things because HDMI can be tricksy.

Mic up your presenter, and check the audio in every scene - nothing worse than recording the whole event and finding out later that one of your presents wasn’t recording any sound. Ask your speaker to stand where they’re going to stand, check the camera angle. I clip the other mic on to myself so I can introduce the speaker, repeat questions, etc. and have it come through on the recording.

Then bring up the title slide, hit Record, introduce the talk, and then switch scenes as you go, and all being well, you’ll end up with a MP4 file you can upload straight to YouTube when you get home.

Shopping List:

Cables:

5 metres seems to be the sweet spot for cable length. Too short, and you’re going to be in the way. Too long and you get weird signal problems, or no signal at all.

  • MicroHDMI > HDMI cable (camera HDMI OUT > Camlink HDMI)
  • Regular HDMI cable (presenter laptop > GameCapture IN)
  • USB-C cable (GameCapture OUT > OBS)

Total investment if you’re starting from scratch will come to just over £1200.

How much?!

OK, you don’t need to spend that all in one go. Get a copy of Reincubate Camo and use your phone instead of buying an SLR camera + Camlink. For slides, you can pick up cheap HDMI splitter and an HDMI > USB capture gizmo on Amazon for about £20 each, and see how you go.

I wouldn’t skimp on microphones, though: as they say in Hollywood, “your ears never blink”, and you can probably get away with significantly worse video quality if the audio is up to scratch.

Here’s two clips I recorded using the old camera-mounted RØDE shotgun mic:

and here’s some recorded with the AnkerWork mics:

So, that’s your lot. Good luck with it, and let me know if you found this useful.

"Recording Meetups" was posted by Dylan Beattie on 20 February 2024 • permalink

Custom Validation Attributes in ASP.NET Core 8

ASP.NET Core 8 rocks. It’s fast, powerful, cross-platform… and, yes, includes jQuery.

Chill. jQuery’s fine. It’s solid, it’s proven, it works, and we’re not here to impress hipsters hiring frontend devs for their Web3 startup, we’re here to build stuff that works (and will probably still work next year.)

Out of the box, ASP.NET Core includes jQuery, jquery.validate and jquery.validation.unobtrusive, which together provide client-side validation that integrates really closely with the server-side validation provided by ASP.NET and System.ComponentModel.

Most of the stuff you’ll find online about how to extend ASP.NET Core validation still works, but there’s a few new things in .NET 8 which make it a little cleaner. Specifically, IDictionary<TKey,TValue> in .NET 8 includes a .TryAdd(key, value) method, which means the MergeAttributes helper method you’ll find in lots of examples of custom validation attributes isn’t required any more.

Here’s how to extend ASP.NET with a custom validation attribute that’ll make a checkbox a required field – the classic “You must accept the terms and conditions” scenario.

The attribute itself:

public class MustBeTrueAttribute : ValidationAttribute, IClientModelValidator {

	public override bool IsValid(object? value) => value is true;

	public void AddValidation(ClientModelValidationContext context) {
		var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
		context.Attributes.TryAdd("data-val", "true");
		context.Attributes.TryAdd("data-val-must-be-true", errorMessage);
	}
}

You’ll need to add two lines of custom JavaScript to your pages - I add these to the end of _ValidationScriptsPartial.cshtml.

<script>
	jQuery.validator.addMethod("must-be-true", (_, element) => element.checked);
	jQuery.validator.unobtrusive.adapters.addBool("must-be-true");
</script>

Don’t put this code inside the jQuery onload handler (the one that’s normally wrapped in $(function() { }) – it doesn’t rely on any DOM elements and the call to jquery.validator.unobtrusive.adapters.addBool() has to run before the validation code parses your form.

To use this attribute in your view model:

public class SignupPostData {
	[MustBeTrue(ErrorMessage = "You must accept the terms and conditions")]
	public bool AcceptTerms { get; set; }
}

and then in your Razor view:

@model SignupPostData

<form method="post">
    <label asp-for="@Model.AcceptTerms">
        <input type="checkbox" asp-for="@Model.AcceptTerms"/>
        I accept the terms and conditions
    </label>
    <span class="form-text text-danger" asp-validation-for="@Model.AcceptTerms"></span>
</form>

@section Scripts {
	@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}

or, if you prefer using the HTML Helper syntax:

@model SignupPostData

@using (Html.BeginForm(FormMethod.Post)) {
	@Html.ValidationSummary()
	@Html.LabelFor(model => model.AgreeToPayment)
	@Html.CheckBoxFor(model => model.AgreeToPayment)
	@Html.ValidationMessageFor(model => model.AgreeToPayment)
	<input type="submit"/>
}

@section Scripts {
	@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
"Custom Validation Attributes in ASP.NET Core 8" was posted by Dylan Beattie on 24 January 2024 • permalink

Choosing a Chat App For Your Tech Event

WhatsApp, Telegram, Slack, Discord, Signal, Teams… seems like every event you go to these days has at least one “official” chat app, and probably at least half-a-dozen unofficial chat groups set up by attendees and speakers.

If you’re running a conference or seminar, should you set one up? And if so, which one should you use?

Let’s get the inevitable out of the way first: even a small community conference is going to attract a few dozen people, and there’s no way they’re going to agree on which chat app is best. Microsoft Teams is great when all the people in it work for the same company; for guests, even signing in can be a showstopper. For everybody who likes WhatsApp because it’s ubiquitous, there’s somebody who refuses to install it because they won’t use Meta products. For everybody who likes Slack because it’s already installed on their company laptop, there’s somebody who hates Slack because of its convoluted authentication system (hi!). For everybody who loves Discord because it’s where all their friends hang out, there’s somebody who can’t use it on their laptop because corporate IT think it’s only for gamers and crypto.

a woman in clown make-up juggling icons for popular chat systems - WhatsApp, Signal, Discord, Telegram, Slack, Teams

That said, having a chat system for folks participating in your event is an excellent idea. For first-time attendees who don’t know anybody, it’s a great way to connect with people, find out where people are having drinks or meeting for dinner. It’s an easy way for organisers to share updates and last-minute agenda changes.

If you’re running coding workshops as part of your event, setting up a dedicated channel for each workshop works brilliantly for sharing code snippets, URLs and diagrams. When I’m teaching workshops, I always ask the organisers to set up a chat with all the attendees, using something people can run on their laptops – there’s no point sharing 25 lines of JavaScript with somebody if the only place they can see it is a WhatsApp notification on their iPhone.

So, here’s a few guiding principles I’d encourage you to consider if you’re creating a chat for your event.

First, ask yourself: are you supporting a physical event, or creating an online community?

It’s lovely to think that people can use your chat to keep in touch after the event, but they don’t need your event chat to do that. Social media exists. Attendees can spin up their own chats and groups any time, on whatever platform they want to, and keep the conversation going there – somewhere where it isn’t your responsibility to answer questions, provide event support, and potentially enforce your event’s code of conduct. Doing those things well for three days is hard enough; do you really want to do it all year round, for free?

Online communities are inclusive: anybody can join, just send them the link. In-person events are exclusive: you’ve actually got to show up. If you’re not there, you’re not part of it. Sure, there’s a huge amount of overlap, but the kind of chat that happens when twenty people are together in an unfamiliar city trying to find a good sushi place is not the same as the chat that happens when those same twenty people are back home, spread across a dozen timezones, and somebody’s asking if anybody knows a good way to show Git commits in PowerPoint.

Consider creating a workspace just for your event: one of the big advantages of creating a new instance/server/workspace/whatever for each event is that it’s easy to invite the right people, and easy to shut it down when you’re done.

Use channels sparingly

You probably need a general chat, a speaker chat, an announcements channel, and if you have separately ticketed events like workshops or seminars, create a channel for each of those. Don’t create fifty different channels for web, JavaScript, ethics, .NET, IOT, crypto, Java: you’ll overwhelm people with choice, and when they see most of those channels are empty and nobody’s posted anything, they’ll lose interest.

Use familiar tools

Online platforms like Pine and gathertown serve a purpose, but the majority of your participants won’t have used them before, which makes them far less likely to get involved in discussion. Stick to something people already know.

During the COVID lockdowns when many events went entirely online, I saw far more engagement and participation during events that used Slack and Discord than I did in events relying on proprietary platforms - we’re talking thousands of chat messages over a few days, vs fewer than a dozen on some of the dedicated event platforms. Familiarity is important.

Encourage your team to get involved

Folks will use chat to ask questions, ask for help, ask if anybody has an extension cord or a Macbook charger or where the vegetarian food is served at lunchtime. Make sure your event staff are around to give helpful answers.

Don’t rely on it if something’s urgent

One of the things I do before I give a talk is to put my phone in airline mode. You ping me on WhatsApp five minutes before I give a presentation? I’m not going to see that until after I’m done. If you need to talk to somebody urgently, call them – and if that doesn’t work, go and find them.

Shut it down when you’re done

This is the one that I personally find the most frustrating. I’m still in Slack workspaces for events that took place in 2020, and like the Hotel California, I can sign out any time I like but I can never leave. Whenever I set up Slack on a new device, they’re all still listed there. I have Telegram chats and WhatsApp groups and Signal chats for old events… and, once in a while, somebody will try to get hold of me via one of those chats. You know if you archive a WhatsApp chat it doesn’t tell other people you’ve archived it… so they have no idea you’re not seeing their messages?

Go on. Delete your old event chats - and I mean delete. Gone. Forever.

Think how much better you’ll feel.

If it was up to me?

Set up a Slack a few months early. Use it to coordinate programme committee, crew, volunteers. Create an announcements channel, a speaker channel and a hallway track for general chit-chat. A few days before you open the doors, invite everybody who’s going to be there. Keep it open for a week or so after the event. Post an announcement that you’ll be shutting it down, give folks the chance to share their LinkedIn, Twitter, email - and then delete the workspace.

"Choosing a Chat App For Your Tech Event" was posted by Dylan Beattie on 18 January 2024 • permalink

Fixing 'Version conflict detected for Microsoft.CodeAnalysis.Common'

I’ve been upgrading projects from .NET 7 to .NET 8, and mostly it’s been remarkably smooth: edit the .csproj files, replace net7.0 with net8.0, then upgrade any 7.x NuGet packages to their 8.x counterparts, and so far, it’s just worked.

Today I got a weird error message:

error: NU1107: Version conflict detected for Microsoft.CodeAnalysis.Common. Install/reference Microsoft.CodeAnalysis.Common 4.5.0 directly to project Rockaway.WebApp to resolve this issue.

error:  Rockaway.WebApp -> Microsoft.EntityFrameworkCore.Tools 8.0.0 -> Microsoft.EntityFrameworkCore.Design 8.0.0 -> Microsoft.CodeAnalysis.CSharp.Workspaces 4.5.0 -> Microsoft.CodeAnalysis.Common (= 4.5.0)
error:  Rockaway.WebApp -> Microsoft.VisualStudio.Web.CodeGeneration.Design 7.0.10 -> Microsoft.DotNet.Scaffolding.Shared 7.0.10 -> Microsoft.CodeAnalysis.CSharp.Features 4.4.0 -> Microsoft.CodeAnalysis.Common (= 4.4.0).

I’m a big fan of error messages that tell you how to fix the error which caused them, like “Install/reference Microsoft.CodeAnalysis.Common 4.5.0 directly to project Rockaway.WebApp to resolve this issue.” in this one… except, if you look at the notes for Microsoft.CodeAnalysis.Common, it quite clearly says:

“Do not install this package manually, it will be added as a prerequisite by other packages that require it.”

Here’s what’s actually going on.

Before upgrading, my project uses two NuGet packages, both version 7.x because they shipped with .NET 7:

  • Microsoft.EntityFrameworkCore.Tools 7.0.12
  • Microsoft.VisualStudio.Web.CodeGeneration.Design 7.0.10

Both of these packages have an indirect dependency on Microsoft.CodeAnalysis.Common 4.4.0 – this doesn’t appear anywhere in the .csproj file, so I’m guessing it’s controlled by this line:

<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

So you try to upgrade Microsoft.EntityFrameworkCore.Tools to 8.x and it fails, because that package requires Microsoft.CodeAnalysis.Common 4.5.0, but your project includes Microsoft.VisualStudio.Web.CodeGeneration.Design 7.x, which requires Microsoft.CodeAnalysis.Common v4.4.0

The solution that worked for me was to remove the other package (v7.x), upgrade EF Core Tools, then install the other package (which installs the latest version, 8.x):

dotnet remove package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools --version 8
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
"Fixing 'Version conflict detected for Microsoft.CodeAnalysis.Common'" was posted by Dylan Beattie on 17 January 2024 • permalink