1
\$\begingroup\$

I'm working on set of methods that locate DVR surveillance footage, based on timestaps.

The files are in the following format:

<drive>\<year>\<month>\<day>\<camera>\<hour>-<minute>-<second>.<extension>

Example: Z:\2011\07\02\000001\00-13-15.mkv through Z:\2011\07\02\000001\23-45-13.mkv

The videos are in 15ish minute increments, that start roughly around the turn of the hour. The edge case, as you could imagine, is around that turn of the hour mark. I have come up with two methods that get the job done, but are extremely convoluted, and frankly ugly.

Can any one think of a less complex way to do this?

EDIT: After some thought, I refactored it into three methods. Splitting out the path building as I had with the file name. It's better, but I still feel it can be simpler. This also resolves the 'change of day' issue I had mentioned below.

    public static string GetSecurityFootage(DateTime timeStamp, Camera camera)
    {
        string storageLoc = Initialization.DvrStorageLocation;

        // Just bail if we don't have a valid storage location.
        if (!Directory.Exists(storageLoc))
        {
            return "";
        }

        // Get path and file name based on timestamp.
        string path = GetVideoPath(timeStamp, camera, storageLoc);
        string fileName = GetVideo(timeStamp, path.ToString());

        if (fileName != "" && File.Exists(path + fileName))
        {
            path = path + fileName;
        }

        // Try the end of the last hour/day.
        else
        {
            // Subtract from time minutes +1 to set time to last hour.
            timeStamp = timeStamp.AddMinutes(-(timeStamp.Minute + 1));

            path = GetVideoPath(timeStamp, camera, storageLoc);
            fileName = GetVideo(timeStamp, path.ToString());

            if (fileName != "" && File.Exists(path + fileName))
            {
                path = path + fileName;
            }
            else
            {
                // If it's not it the last hour, we do not have footage.
                return "";
            }
        }

        return path;
    }

    private static string GetVideoPath(DateTime timeStamp, Camera camera, string storageLoc)
    {
        string pathSep = Path.DirectorySeparatorChar.ToString();
        string cameraPath = "";

        string year = timeStamp.Year.ToString();
        string month = timeStamp.Month.ToString();
        string day = timeStamp.Day.ToString();

        StringBuilder path = new StringBuilder();

        // Paths are preceded by 0.
        if (day.Length == 1)
        {
            day = "0" + day;
        }

        if (month.Length == 1)
        {
            month = "0" + month;
        }

        switch (camera)
        {
            case Camera.Patio:
                cameraPath = "000001";
                break;

            case Camera.Counter:
                cameraPath = "000002";
                break;

            case Camera.Shed:
                cameraPath = "000003";
                break;

            case Camera.Cafe:
                cameraPath = "000004";
                break;

            case Camera.Lobby:
                cameraPath = "000005";
                break;

            default:
                cameraPath = "000002";  //default to counter.
                break;
        }

        // Path to video.
        path.Append(storageLoc);

        if (!storageLoc.Trim().EndsWith(pathSep))
        {
            path.Append(pathSep);
        }

        path.Append(year);
        path.Append(pathSep);
        path.Append(month);
        path.Append(pathSep);
        path.Append(day);
        path.Append(pathSep);
        path.Append(cameraPath);
        path.Append(pathSep);


        // The path doesn't exist, we will not have footage.
        if (!Directory.Exists(path.ToString()))
        {
            return "";
        }

        return path.ToString();
    }

    private static string GetVideo(DateTime timeStamp, string path)
    {
        string hour = timeStamp.Hour.ToString();
        int minute = timeStamp.Minute; //leave as int for comparison.

        string fileName = "";

        if (hour.Length == 1)
        {
            hour = "0" + hour;
        }

        // Get all the files in the dir, but just the file names.
        var files = Directory.GetFiles(path.ToString()).Select(f => Path.GetFileName(f));

        // All files that start with the correct hour.
        var q = from f in files
                where f.Trim().StartsWith(hour)
                orderby f
                select f;

        // Now, loop the files for the higest that is still less than the minute we're looking for.
        foreach (var file in q)
        {
            int min = 0;
            int.TryParse(file.Substring(3, 2), out min);

            // Set file name for each loop; when done the highest should always be the last one set.
            if (min <= minute)
            {
                fileName = file;
            }
        }

        return fileName;
    }

I guess there is no strike-through... disregard the following:

One thing that is noticeable is that for the change of day, it will fail to find the footage. This isn't a huge deal, this is only a convenience feature for our POS. In this case, the user will just have to browse to the video manually. The change of hour is important, otherwise this feature is useless 1/4 of the time.

\$\endgroup\$
5
\$\begingroup\$

Looking at how you try to solve this problem, you try to guess the expected file name of this point in time. Allow me to suggest another approach which is more natural and simpler IMHO.

If all these videos are sorted, a footage is in a video if it's timestamp is within the time of this video, e.g. if you have a video Z:\2011\07\02\000001\13-45-13.mkv and the next video is Z:\2011\07\02\000001\14-00-32.mkv, and you are looking for the footage at 2011-07-02 13:52:07, you know that the footage it's in the first one.

To do this, you need to

  1. Sort the videos by their starting date (which you will get by parsing the name).
  2. Search for the video containing this footage timestamp.

This also handles all your edge cases :).

But I still have some comments about your code.

  1. Don't do path.ToString() when path is already a string.
  2. String.format is more readable than StringBuilder in your case.
  3. What if you add another camera? You will have to modify and recompile your code. Consider defining cameras externally.
\$\endgroup\$
  • \$\begingroup\$ Thanks for all the helpful feedback. I noticed the path.ToString() after I edited, it was just left over. I used StringBuilder because of the if in the middle of it, but I moved that check up, and changed to string.Format, now I only need one pathSep. The choice to build the cameras as a enum was one of security; so there is not some cameras.xml that a user could delete. \$\endgroup\$ – John Smith Jul 28 '11 at 13:31
1
\$\begingroup\$

String.IsnullOrEmpty instead of blank string comparisons. Also return immediately in your if and remove the else, put the else contents in a seperate explicitely named method which you return directly.

\$\endgroup\$
  • \$\begingroup\$ Good call on returning after from the first if. The nested if statement was really bugging. But, what benefit would it be to add another method after the if? If it returns on the first if, I could leave the second hour check under it without calling fourth method. \$\endgroup\$ – John Smith Jul 28 '11 at 13:31
0
\$\begingroup\$

Just break the method in a smaller ones and move all of them in a separate class that will be used only for creating a path to a file. It might be the builder as a loki2302 said or just a provider

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.