media comments edit

Back in March 2014 I started converting my DVD rips into MP4 files for use with Plex. I ran two laptops (both with 2.3GHz dual-core CPUs) 24/7 until early March 2015 when I added a third computer - an eight-core 4GHz machine.

Today I finally finished converting all of my disc-based video content to MP4.

Some quick statistics:

  • Total number of files: 4998
  • Total content runtime: 134 days, 8 hours, 56 minutes, 47 seconds
    • SD runtime: 115 days, 12 hours, 25 minutes, 17 seconds
    • HD runtime: 18 days, 20 hours, 31 minutes, 30 seconds
  • Total file size: 5182.3GB
    • SD file size: 3042.04GB
    • HD file size: 2140.26GB
  • Average MB/minute for SD content: 18.73
  • Average MB/minute for HD content: 80.72

I’m pretty pleased with how everything has come together. Seeing it all in Plex, nicely organized… it’s a good feeling.

I can definitely say CPU power is important in video conversion. My laptops could convert an average SD movie in three or four hours, but an HD movie… I couldn’t get one converted in a day. The eight-core behemoth can take the same SD movie and finish in an hour or less; and HD movies take about four hours - same as SD content on my laptops.

Anyway, if you’re looking to convert a bunch of video, it’s worth investing in some hefty CPU power. It’ll save you tons of time.

Finally, as part of this, I’d like to introduce my media center documentation on ReadTheDocs.

It’s a work in progress, so this is sort of a “soft launch,” but I think it’s fleshed out enough to be of some use. I will probably do a more dedicated blog entry for it when I’ve got more of it filled out.

Information about how I converted my stuff with Handbrake, including the script I used to pull the report data above, as well as the specs for my behemoth conversion/Plex server, is all over there.

process, security comments edit

I feel like I should write a book. It’d be epic like Moby Dick but would start with, “Call me Yossarian.” This is going to sound confusing and comedic, straight out of Catch-22, but I assure you it’s entirely true. It is happening to me right now.

Serenity Now!

We write a lot of documentation to a wiki at work. I’ve got permissions on it to add pages, rename pages, move pages… but not delete pages. If I want to delete a page, I have to find someone who has delete rights and ask them to do that, which doesn’t make sense because I’m a pretty heavy contributor to the wiki.

I decided to seek out delete permissions for myself.

The wiki is managed by an overseas team. The previous process to get permissions to the wiki was to send an email to their infrastructure distribution list with your request and the issue would be dealt with in a day or two. It was fairly effective from a customer perspective.

The new process to get wiki permissions is to file a ticket in this custom-built ticketing system they’ve adopted. You find this out by sending an email to the infrastructure distribution list and reading the “out of office” autoresponder thing that comes back.

You can’t file a ticket unless you have an account on the ticketing system. That’s… well, not unheard of, but a bit inconvenient. Fine, I need to create an account.

In order to get an account on the ticketing system, you need to file a ticket. No joke. As one colleague put it, this is sort of like a secret society - you can’t get in unless you already know someone who’s in and will “vouch for you” by creating a ticket on your behalf.

Three working days later, I have an account so I log in. The ticketing system is a totally custom beast that was initially written starting in 2001 and hasn’t really been updated since 2008. It looks and behaves exactly like you think - it’s very bare-bones, there’s no significant help, and it’s entirely unintuitive to people who don’t already use it every day.

Seeking out help, I notice in the autoresponder email there’s a wiki link to a guide on how to file tickets. Cool. I visit that link and… I don’t have permissions to see the wiki link.

In order to see the guide on how to file tickets, I have to file a ticket. Of course, I’m not sure what kind of ticket to file, since I can’t see the guide.

I search around to see if there’s any hint pointing me to which ticket type to file since they all have great titles like “DQT No TU Child Case.” Totally obvious, right? I end up stumbling onto a screen shot someone has taken and posted to a comment section on an unrelated wiki page referring me to the type of case I need to file.

I don’t see the right case type on the list of available tickets I can file. Turns out I don’t have ticket system permissions to file that kind of ticket.

I have now opened a ticket so I can get permissions to open a ticket to get permissions to delete pages from the wiki. This is after, of course, the initial “secret society” ticket was filed to get me an account so I can file tickets.

humor, rest comments edit

I was browsing around the other day and found your mom’s REST API. Naturally, I pulled my client out and got to work.

An abbreviated session follows:

GET /your/mom HTTP/1.1

HTTP/1.1 200 OK

PUT /your/mom HTTP/1.1
":)"

HTTP/1.1 402 Payment Required

POST /your/mom HTTP/1.1
"$"

HTTP/1.1 411 Length Required

PUT /your/mom HTTP/1.1
":)"

HTTP/1.1 406 Not Acceptable
HTTP/1.1 413 Request Entity Too Large
HTTP/1.1 200 OK
.
.
.
HTTP/1.1 200 OK
.
.
HTTP/1.1 200 OK
.
HTTP/1.1 200 OK
HTTP/1.1 200 OK
HTTP/1.1 200 OK
HTTP/1.1 502 Bad Gateway
HTTP/1.1 503 Service Unavailable

I think I need to get a new API key before she gives me the ol’ 410. :)

build comments edit

In making a package similar to the NuGet.Server package, I had a need to, from one project in the solution, get the list of build output assemblies from other projects in the same solution.

That is, in a solution like:

  • MySolution.sln
    • Server.csproj
    • Project1.csproj
    • Project2.csproj

…from the Server.csproj I wanted to get the build output assembly paths for the Project1.csproj and Project2.csproj projects.

The technically correct solution is sort of complicated and Sayed Ibrahim Hashimi has documented it on his blog. The problem with the technically correct solution is that it requires you to invoke a build on the target projects.

That build step was causing no end of trouble. Projects were re-running AfterBuild actions, code was getting regenerated at inopportune times, cats and dogs living together - mass hysteria.

I came up with a different way to get the build outputs that is less technically correct but gets the job done and doesn’t require you to invoke a build on the target projects.

My solution involves loading the projects in an evaluation context using a custom inline MSBuild task. Below is a snippet showing the task in action. Note that the snippet is in the context of a .targets file that would be added to your .csproj by a NuGet package, so you’ll see environment variables used that will only be present in a full build setting:

<Project DefaultTargets="EnumerateOutput" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
  <ItemGroup>
    <!-- Include all projects in the solution EXCEPT this one -->
    <ProjectToScan Include="$(SolutionDir)/**/*.csproj" Exclude="$(SolutionDir)/**/$(ProjectName).csproj" />
  </ItemGroup>
  <Target Name="EnumerateOutput" AfterTargets="Build">
    <!-- Call the custom task to get the output -->
    <GetBuildOutput ProjectFile="%(ProjectToScan.FullPath)">
      <Output ItemName="ProjectToScanOutput" TaskParameter="BuildOutput"/>
    </GetBuildOutput>

    <Message Text="%(ProjectToScanOutput.Identity)" />
  </Target>

  <UsingTask TaskName="GetBuildOutput" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll" >
    <ParameterGroup>
      <ProjectFile ParameterType="System.String" Required="true"/>
      <BuildOutput ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true"/>
    </ParameterGroup>
    <Task>
      <Reference Include="System.Xml"/>
      <Reference Include="Microsoft.Build"/>
      <Using Namespace="Microsoft.Build.Evaluation"/>
      <Using Namespace="Microsoft.Build.Utilities"/>
      <Code Type="Fragment" Language="cs">
      <![CDATA[
        // The dollar-properties here get expanded to be the
        // actual values that are present during build.
        var properties = new Dictionary<string, string>
        {
          { "Configuration", "$(Configuration)" },
          { "Platform", "$(Platform)" }
        };

        // Load the project into a separate project collection so
        // we don't get a redundant-project-load error.
        var collection = new ProjectCollection(properties);
        var project = collection.LoadProject(ProjectFile);

        // Dollar sign can't easily be escaped here so we use the char code.
        var expanded = project.ExpandString(((char)36) + @"(MSBuildProjectDirectory)\" + ((char)36) + "(OutputPath)" + ((char)36) + "(AssemblyName).dll");
        BuildOutput = new TaskItem[] { new TaskItem(expanded) };
      ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

How it works:

  1. Create a dictionary of properties you want to flow from the current build environment into the target project. In this case, the Configuration and Platform properties are what affects the build output location, so I pass those. The $(Configuration) and $(Platform) in the code snippet will actually be expanded on the fly to be the real values from the current build environment.
  2. Create a tiny MSBuild project collection (similar to the way MSBuild does so for a solution). Pass the set of properties into the collection so they can be used by your project. You need this collection so the project doesn’t get loaded in the context of the solution. You get an error saying the project is already loaded if you don’t do this.
  3. Load the project into your collection. When you do, properties will be evaluated using the global environment - that dictionary provided.
  4. Use the ExpandString method on the project to expand $(MSBuildProjectDirectory)\$(OutputPath)$(AssemblyName).dll into whatever it will be in context of the project with the given environment. This will end up being the absolute path to the assembly being generated for the given configuration and platform. Note the use of (char)36 there - I spent some time trying to figure out how to escape $ but never could, so rather than fight it… there you go.
  5. Return the information from the expansion to the caller.

That step with ExpandString is where the less technically correct bit comes into play. For example, if the project generates an .exe file rather than a .dll - I don’t account for that. I could enhance it to accommodate for that, but… well, this covers the majority case for me.

I considered returning a property rather than an item, but I have a need to grab a bunch of build output items and batch/loop over them, so items worked better in that respect.

There’s also probably a real way of escaping $ that just didn’t pop up in my searches. Leave a comment if you know; I’d be happy to update.

sublime, xml, gists comments edit

I already have my build scripts tidy up my XML configuration files but sometimes I’m working on something outside the build and need to tidy up my XML.

There are a bunch of packages that have HTML linting and tidy, but there isn’t really a great XML tidy package… and it turns out you don’t really need one.

  1. Get a copy of Tidy and make sure it’s in your path.
  2. Install the Sublime package “External Command” so you can pipe text in the editor through external commands.
  3. In Sublime, go to Preferences -> Browse Packages... and open the “User” folder.
  4. Create a new file in there called ExternalCommand.sublime-commands. (The name isn’t actually important as long as it ends in .sublime-commands but I find it’s easier to remember what the file is for with this name.)

Add the following to the ExternalCommand.sublime-commands file:

[
    {
        "caption": "XML: Tidy",
        "command": "filter_through_command",
        "args": { "cmdline": "tidy --input-xml yes --output-xml yes --preserve-entities yes --indent yes --indent-spaces 4 --input-encoding utf8 --indent-attributes yes --wrap 0 --newline lf" }
    }
]

Sublime should immediately pick this up, but sometimes it requires a restart.

Now when you’re working in XML and want to tidy it up, go to the command palette (Ctrl+Shift+P) and run the XML: Tidy command. It’ll be all nicely cleaned up!

The options I put here match the ones I use in my build scripts.. If you want to customize how the XML looks, you can change up the command line in the ExternalCommand.sublime-commands file using the options available to Tidy.