Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

tv2html: Catalog your TV shows with offline browsable HTML

5.00/5 (1 vote)
10 Apr 2024MIT5 min read 3.6K   31  
Generate a web browsable interface to your archived TV show content
Create offline browsable content that provides a web based interface to your TV shows.

Image 1

Introduction

I have some shows that I bought years ago on DVD and Bluray that I archived because I hate disk swapping, and I like having backups.

The issue is browsing the shows, which leaves me with plain jane Windows File Explorer or some sort of DLNA service connected to a smart TV, which is fine when available but it isn't always, like when traveling.

What I've done is create a command line utility that creates a web browsable interface to a TV show of your choosing. It uses themoviedb.org's REST API to retrieve the information about your show. You can then browse to your show's root directory's index.html file and it will present a navigable interface to all your videos, complete with series, season and episode information.

Disclaimer: Some readers may see this and think "Wow, I can use that with my pirated media!" - to those readers, I can't stop you from using this how you will, but I don't condone it. Also, practically speaking, it's usually cheaper just to pay for what you want than to run a VPN anyway.

Prerequisites

  • You'll need a machine that can build a Visual Studio .NET C# console application project
  • You'll need an Authorization Token from themoviedb.org. Create a free account, log in, go the the account menu Settings|API to create or retrieve your Authorization Token. Don't confuse it with the API key

Update: Added config file support. Bug fixes.

Background

Most of this project is just ersatz "ASP.NET pages" that bind to JSON returned from a REST API. My csppg project is used for turning the .aspx files into C# code that can be executed from the application. The csppg executable is referenced as pre-build events. 

To facilitate databinding to the JSON we use a variation of this code.

There is a core Tmdb object that handles some of the core functionality of the app, like doing a REST query, or downloading a file.

To handle command line parsing and other minutiae this project uses my Program.Base project. 

Using the application

Below, AUTH_TOKEN should be a big ugly hash value you get from themoviedb website.

Search for all shows with "burn" as part of the name and list them:

BAT
tv2html "burn" AUTH_TOKEN

Search for "Burn Notice" - will generate to the "TV" subdirectory as it will be the only match:

BAT
tv2html "burn notice" AUTH_TOKEN /output TV

Generate the show id 2919 (Burn Notice) and write the result to the current directory.

BAT
tv2html #2919 AUTH_TOKEN

Note that you can omit the auth token on the command line by providing it in a tv2html.config file in the executable's working directory. The config file is similar to App.config in .NET framework projects, but doesn't include the executable file extension for cross platform reasons:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="authToken" value="AUTH_TOKEN"/>
  </appSettings>
</configuration>

Replace AUTH_TOKEN with your auth token. A template is provided in the solution folder.

Several things:

  • The directory structure created follows the form of [Series]\[Season]\[S0#E0# Episode Name]
  • The index.html files are your main entry point. There is one in the series folder and each season folder.
  • Folders and images that were already created or downloaded will not be created or downloaded again. Delete them if you want to ensure you have the latest from The Movie DB.
  • During the generation process, the code looks for the presence of video files in the folders. If it finds one in a supported format the episode page will embed the video. For example, Burn Notice\Season 1\S01E01 Pilot.(mp4|webm|mpeg|ogg) will be hunted for. Otherwise it will be listed as unavailable. If you add more videos later, you will want to rerun tv2html to regenerate your pages.

Coding the application

Most of the work is done by the "aspx" files. These aren't real ASP.NET pages. They don't support the same directives, for starters, and they are C# only. Plus there is no web server involved. Instead, the csppg tool in the root solution folder is used to turn these aspx-ish files into C# code that can be called from the application. The reason we use this method, and the .aspx extension is to get intellisense that the T4 engine doesn't provide, and plus this tool doesn't require as much buy in as T4.

The main Program.cs's Run() method just handles last mile argument logic and the initial fetch, plus running the templates.

  • series.index.aspx is responsible for the top level index.html.
  • season.index.aspx is responsible for each season's index.html.
  • episode.aspx is responsible for each episode's html file.

If you look at the content of those files you'll see heavy use of the dynamic keyword. This allows C# to late bind to our JSON elements almost as though they were native static typed objects in C#.

Otherwise, the index files are just flexible grid layouts with season or episode data in them. Each page has a flyout navigation sidebar. The episode page has a video, or the still image if a video could not be found.

Of particular interest is the Tmdb.cs file which handles some critical application functionality, including exposing the JSON elements as late bindable objects, interfacing with a REST server, and downloading files. I've covered the late binding to JSON in a linked article in the introduction, but I've posted the other functionality from that file here:

C#
public static string GetSafeFilename(string file)
{
    var result = "";
    var inv = Path.GetInvalidFileNameChars();
    var sb = new StringBuilder();
    sb.Clear();
    for (int j = 0; j < file.Length; j++)
    {
        if (Array.IndexOf(inv, file[j]) > -1)
        {
            sb.Append('_');
        }
        else
        {
            sb.Append(file[j]);
        }
    }
    result = Path.Combine(result, sb.ToString());
        
    return result;
}
public static JsonDocument GetJson(string url)
{
    JsonDocument result;
    using (var msg = new HttpRequestMessage(HttpMethod.Get, url))
    {
        msg.Headers.Clear();
        msg.Headers.Add("accept", "application/json");
        msg.Headers.Add("Authorization", "bearer " + AuthToken);
        using (var resp = _client.Send(msg))
        {
            result = JsonDocument.Parse(resp.Content.ReadAsStream());
        }
    }
    return result;
}
public static void Download(string url, string path, bool overwrite = false)
{
    if (overwrite || !File.Exists(path))
    {
        using (var msg = new HttpRequestMessage(HttpMethod.Get, url))
        {
            using (var resp = _client.Send(msg))
            {
                var dir = Path.GetDirectoryName(path);
                if(!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir!);
                }
                using (var outstm = File.OpenWrite(path))
                {
                    resp.Content.ReadAsStream().CopyTo(outstm);
                }

            }
        }
    }
}
public static object GetObject(string url)
{
    return new TmdbElement(GetJson(url).RootElement);
}

Nothing there is particularly complicated. The GetSafeFilename() function just turns invalid filename characters into underscores. The rest is even more self explanatory.

Now the main mess is our ASPX files which handle all the logic, and call the functions above. They're not formatted in a way that's conducive to putting the source here, but take a look at them. If you're familiar with ASP.NET it shouldn't be difficult to understand.

History

  • 10th April, 2024 - Initial submission
  • 10th April, 2024 - Added config file support, bugfixes

License

This article, along with any associated source code and files, is licensed under The MIT License