Skip to content

Commit

Permalink
Add project files.
Browse files Browse the repository at this point in the history
  • Loading branch information
fellypsantos committed Jun 11, 2023
1 parent acaf8c4 commit 94329bc
Show file tree
Hide file tree
Showing 11 changed files with 433 additions and 0 deletions.
6 changes: 6 additions & 0 deletions App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
30 changes: 30 additions & 0 deletions FFMPEG.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace StreamLapse
{
public class FFMPEG
{
public static bool CaptureStreamFrame(string streamURL, string filenameFullPath)
{
Console.WriteLine("\nRequesting new frame...");

using (Process p = new Process())
{
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.FileName = "ffmpeg.exe";
p.StartInfo.Arguments = $"-rtsp_transport tcp -i {streamURL} -vframes 1 {filenameFullPath}";
p.Start();
p.WaitForExit();

return File.Exists(filenameFullPath);
}
}
}
}
19 changes: 19 additions & 0 deletions Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using CommandLine;

namespace StreamLapse
{
public class Options
{
[Option(Required = true, HelpText = "RTSP url to your IP camera.")]
public string Stream { get; set; }

[Option("interval", Default = 5000, HelpText = "Time im milliseconds to wait before save another frame.")]
public int CaptureInterval { get; set; }

[Option(HelpText = "Path to store the frames, will be created if not exists.")]
public string Output { get; set; }

[Option(Default = "IMG_", HelpText = "Prefix to use with filename, default results in IMG_1, IMG_2")]
public string Prefix { get; set; }
}
}
39 changes: 39 additions & 0 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using CommandLine;
using System;
using System.IO;

namespace StreamLapse
{
internal class Program
{
static void Main(string[] args)
{
Console.Title = "..:: StreamLapse ::..";

try
{
Parser.Default.ParseArguments<Options>(args).WithParsed(RunOptions);
}

catch (Exception ex) when (ex is FormatException || ex is IOException)
{
Console.WriteLine(ex.Message);
}
}

static void RunOptions(Options options)
{
if (!Validation.IsValidRtspUrl(options.Stream)) throw new FormatException("Stream url is not valid.");

StreamHandler streamHandler = new StreamHandler(options.Stream);

streamHandler.SetCaptureInterval(options.CaptureInterval);

streamHandler.SetOutputDirectory(options.Output);

streamHandler.SetImagePrefix(options.Prefix);

while (true) streamHandler.SaveFrameToJPGFile();
}
}
}
35 changes: 35 additions & 0 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("StreamLapse")]
[assembly: AssemblyDescription("Connect to your IP camera and create a timelapse defining an interval between each frame capture.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("fellypsantos2011@gmail.com")]
[assembly: AssemblyProduct("StreamLapse")]
[assembly: AssemblyCopyright("Copyright © 2023")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("e2b26aa8-f6c8-4182-b18e-d3a5e8d325f5")]

// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
164 changes: 164 additions & 0 deletions StreamHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace StreamLapse
{
public class StreamHandler
{
private readonly string _streamURL;
private string _outputDir;
private string _prefixImageName;
private int _frameCount;
private int _captureIntervalInMilliseconds;
private long _totalBytesWrited;

/// <summary>
/// Handle connection to a camera stream.
/// </summary>
/// <param name="rtsp">Real Time Streaming Protocol something like this: rtsp://user:pass@192.168.0.5/live/mpeg4</param>
public StreamHandler(string rtsp)
{
_streamURL = rtsp;
_frameCount = 0;
_totalBytesWrited = 0;
}

/// <summary>
/// Setup the capture interval to wait before save each frame.
/// </summary>
/// <param name="captureInverval">Time to wait before save another frame.</param>
public void SetCaptureInterval(int captureInverval)
{
_captureIntervalInMilliseconds = captureInverval;
}

/// <summary>
/// Folder to store the saved images.
/// </summary>
/// <param name="outputDirectory">Path to store the frames, will be created if not exists.</param>
public void SetOutputDirectory(string outputDirectory)
{
_outputDir = outputDirectory;

if (string.IsNullOrEmpty(outputDirectory)) return;

if (!Directory.Exists(_outputDir)) Directory.CreateDirectory(_outputDir);
}

/// <summary>
/// Set prefix to be used as filename with frame count eg IMG_1
/// </summary>
/// <param name="prefix">The image prefix</param>
public void SetImagePrefix(string prefix)
{
_prefixImageName = prefix;
}

/// <summary>
/// Takes the filename and concat with output directory to create the full exporting path.
/// </summary>
/// <param name="filename">Filename to merge with output path</param>
/// <returns>Full path to store the image.</returns>
private string GenerateImageNameOutputPath(string filename)
{
if (string.IsNullOrEmpty(_outputDir)) return filename;

return Path.Combine(_outputDir, filename);
}

/// <summary>
/// Connects to stream and save current frame to file.
/// </summary>
/// <exception cref="IOException">In case of the image already exists.</exception>
public void SaveFrameToJPGFile()
{
Stopwatch stopwatch = Stopwatch.StartNew();

string filename = $"{_prefixImageName}{++_frameCount}.jpg";

string filenameFullPath = GenerateImageNameOutputPath(filename);

if (File.Exists(filenameFullPath)) throw new IOException($"Failed to save image {filenameFullPath} because it already exists.");

stopwatch.Start();

bool frameWasSaved = false;

while(!frameWasSaved)
{
frameWasSaved = FFMPEG.CaptureStreamFrame(_streamURL, filenameFullPath);

if (!frameWasSaved) Console.WriteLine($"Failed to save the frame: {filename}. Retrying...");
}

stopwatch.Stop();

RegisterFileSizeInBytes(filenameFullPath);

double timeSpentToSaveFile = TimeSpan.FromMilliseconds(stopwatch.ElapsedMilliseconds).TotalSeconds;

PrintStatus(filenameFullPath, timeSpentToSaveFile);

Thread.Sleep(_captureIntervalInMilliseconds);
}

/// <summary>
/// Accumulate the bytes of all images writed to show while processing.
/// </summary>
/// <param name="filename">File to get size and add to previews bytes.</param>
private void RegisterFileSizeInBytes(string filename)
{
FileInfo fileInfo = new FileInfo(filename);

_totalBytesWrited += fileInfo.Length;
}

/// <summary>
/// Show the main text on screen updating the information on each connection.
/// </summary>
/// <param name="filename">Last file saved.</param>
/// <param name="timeSpent">Time spent in milliseconds to save the last file.</param>
private void PrintStatus(string filename, double timeSpent)
{
Console.Clear();

Console.WriteLine("..:: StreamLapse ::..\n\n");

Console.WriteLine("See the world from different perspective.\n");

Console.WriteLine("created by\n");

Console.WriteLine("fellypsantos2011@gmail.com\n\n\n");

Console.WriteLine($"File {filename} saved in {timeSpent} seconds.\n");

Console.WriteLine("Total frames saved at now: " + _frameCount + '\n');

Console.WriteLine("Total filesize of images: " + FormatFileSize(_totalBytesWrited));
}

/// <summary>
/// Convert bytes to a more user friendly metric like Bytes, KiloBytes, MegaBytes etc.
/// </summary>
/// <param name="fileSize">File size in bytes to be converted.</param>
/// <returns>Converted unit with two decimal places.</returns>
private string FormatFileSize(long fileSize)
{
string[] sizeUnits = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

int unitIndex = 0;
double size = fileSize;

while (size >= 1024 && unitIndex < sizeUnits.Length - 1)
{
size /= 1024;
unitIndex++;
}

return $"{size:0.##} {sizeUnits[unitIndex]}";
}
}
}
95 changes: 95 additions & 0 deletions StreamLapse.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E2B26AA8-F6C8-4182-B18E-D3A5E8D325F5}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>StreamLapse</RootNamespace>
<AssemblyName>StreamLapse</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<IsWebBootstrapper>false</IsWebBootstrapper>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>time-lapse.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="CommandLine, Version=2.9.1.0, Culture=neutral, PublicKeyToken=5a870481e358d379, processorArchitecture=MSIL">
<HintPath>packages\CommandLineParser.2.9.1\lib\net461\CommandLine.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Numerics" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="FFMPEG.cs" />
<Compile Include="Options.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StreamHandler.cs" />
<Compile Include="Validation.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="time-lapse.ico" />
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.7.2">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4.7.2 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
Loading

0 comments on commit 94329bc

Please sign in to comment.