65.9K
CodeProject 正在变化。 阅读更多。
Home

J-AXE:C# 中的文件分割器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.15/5 (9投票s)

2011年7月11日

CPOL

6分钟阅读

viewsIcon

38056

downloadIcon

2045

J-AXE 文件分割器是一款使用 C# .NET 开发的 Windows 应用程序,用于根据大小和分割后的总文件数,按时间间隔分割文件。

引言

J-AXE 文件分割器是一款使用 C# .NET 开发的 Windows 应用程序,用于根据大小和分割后的总文件数,按时间间隔分割文件。我不会忽视这是一个新应用程序的事实。市面上已经有太多开源项目可以实现相同的功能。那么问题来了,为什么我花了这么长时间来开发这个应用程序呢?请允许我解释一下:无论现有的应用程序是开源的还是付费的,你都无法获得按时间间隔或持续时间分割文件的选项。但在这里,您将获得此功能以及其他新颖的功能;最重要的一点是,我想使用 C#.NET 来开发它,通过开发此类应用程序,我将有机会学习新知识,甚至可以利用我的业余时间做一些有意义的事情。所以我这样做了,这是这个J-AXE 文件分割器应用程序的用户界面。

UI.png

图 1. JAXE 文件分割器 UI。

你可能会想,为什么这个应用程序叫 J-AXE?实际上,J 来自Jawed,而AXE是砍木头的斧头。所以,我选择了J-AXE这个应用程序名称。

背景

当然,有些事情触发了我产生这个想法/应用程序。上个月,我用摄像机录制了家庭视频。之后,我添加了一些歌曲和效果到录制的视频中,将其制作成一部电影,以便我可以分发给家人。现在我想按时长而不是按大小分割这个文件,这样我就可以将其分成几个部分,比如第一部分 30 分钟,第二部分 45 分钟。我到处搜索开源解决方案,但找到的都只能按大小分割文件,而不能按时间间隔分割。

然后这个需求在我脑海中激起了某种化学反应,我想出了一个应用程序来满足我的需求,它就是“J-AXE:文件分割器”。

Using the Code

J-AXE:文件分割器 Windows 应用程序使用起来非常方便。整个解决方案分为 2 个项目。一个项目是用户界面,第二个项目就是我们的主要逻辑,以 DLL 的形式存在。将逻辑与 UI 分离并将逻辑作为 DLL 的想法是为了让任何人都能轻松使用它,而无需参考我的 UI 实现。

让我给您一些整个场景的图示(类图)

ClassDiagram.png

图 2. JAXE 文件分割器与 JAXE DLL 的类图。

首先,我想解释一下UI 部分的实现。然后,我将解释逻辑部分。

第一部分:用户界面

框图大致是这样的

JAXE-FileSplitter_New.png

图 3. JAXE 文件分割器的框图。

在 UI 上,用户将获得以下选项来分割文件

  • 按时长(秒/分钟)
  • 按大小(字节/KB/MB)
  • 按文件数量

根据所选选项,我们将调用相应的函数来分割文件

这是调用相应方法的代码

点击“分割”按钮时,我们将调用按钮点击事件函数以获取输入值,如要分割的文件位置、输出目录和选定的选项。

//get the tab selected
int tabSelected = tabControlDuration.SelectedIndex;
//make sure that inputs file and target folder name is provided
if (!string.IsNullOrEmpty(textBoxFileSplit.Text))
{
if (!string.IsNullOrEmpty(textBoxOpFileLocation.Text))
{//get the tab selected for the operation
switch (tabSelected)
{
case 0:
DurationSplit();//call the method to split the file based on duration.
break;
case 1:
SizeSplit();//call the method to split the file based on Size in KB/M/Bytes.
break;
case 2:
NoOfFileSplit();//Call the method to split the the file based on Numbers.
break;
default:
break;
}//end of switch

假设用户点击了“时长”选项卡

在此选项下,用户需要提供时间间隔(例如 30 分钟的间隔),以及他们想要以分钟还是秒为单位的时间间隔(默认情况下为秒)。提供所需的输入后,用户需要点击“分割”按钮。点击此分割按钮后,用户可以在 UI 上看到斧头的动画,好像有什么东西在斧头下。为了在 UI 上显示动画和分割文件,我使用了BACKGROUND WORKER的概念。这样,在分割文件的过程中,UI 不会冻结或卡死。

请允许我解释一下 background worker来源http://www.albahari.com/threading/part3.aspx)。

Background worker 是 System.ComponentModel 命名空间的一部分,用于管理工作线程。

BackgroundWorker 是一个组件,它允许您将一个耗时任务委托给另一个线程。它不仅限于此。您可以将该组件放在 Windows 窗体上(它是一个非 UI 控件,所以它会进入组件托盘)。您可以与它注册事件处理程序。它负责在单独的线程中运行耗时任务,同时在主事件处理线程中运行该任务以更新控件(报告结果或进度)。

BackgroundWorker 有一个 RunWorkerCompleted 事件,在 DoWork 事件处理程序完成工作后触发。在 DoWork 中,BackgroundWorker 将调用我们的 JAXE DLL 来分割文件,完成后,它将调用 RunWorkerCompleted 以通过 UI 向用户显示通知消息,并将所有控件重置为初始状态。

来看代码部分

如果您查看我的代码,我使用了 RunWorkerAsync 并传递了 object 作为参数。这意味着我告诉 BackgroundWorker 异步执行工作。这是执行我们任务的代码

//Method to split the file based on Duration.
private void DurationSplit()
{
var durationIntreval = comboBoxDuration.SelectedIndex;
//Verify the Duration value provided by user
if (!string.IsNullOrEmpty(textBoxDuration.Text))
{
//Convert the duration in seconds
var durationInSeconds = 0.0;
if (durationIntreval == 1)
durationInSeconds=Convert.ToDouble(textBoxDuration.Text)*60;
else
durationInSeconds=Convert.ToDouble(textBoxDuration.Text);
//set the values
var setInPuts = new SetInPuts
{
SourceFileLocation = textBoxFileSplit.Text,
TargetFolderLocation = textBoxOpFileLocation.Text,
DurationToSplit =durationInSeconds,
TabSelected=tabControlDuration.SelectedIndex
}; 
//Enabled the animation
pictureBox1.Visible = true;
//disable the button
buttonSplit.Enabled = false; 
//initialize the BackGround worker
backgroundWorkerForSplitFile = new BackgroundWorker();
//run back ground worker in sync by passing parameter as object.
backgroundWorkerForSplitFile.RunWorkerAsync(setInPuts);
//force the BGW to do some work
backgroundWorkerForSplitFile.DoWork+=new DoWorkEventHandler
			(backgroundWorkerForSplitFile_DoWork);
//Force the GBW to perform some of the task when previous job done
backgroundWorkerForSplitFile.RunWorkerCompleted+=
    new RunWorkerCompletedEventHandler(backgroundWorkerForSplitFile_RunWorkerCompleted);
}//end of if condition
}//end of the method

我们的 Dowork 将在此处,它最终会被 background worker 调用。

private static void backgroundWorkerForSplitFile_DoWork
	(object sender, DoWorkEventArgs e)
{
//
var split = new JAXE.Jaxe();
var inputToPass = e.Argument as SetInPuts;
if (inputToPass != null)
{
switch (inputToPass.TabSelected)
{
case 0:
split.SplitFileBasedOnDuration(inputToPass.SourceFileLocation,
	inputToPass.DurationToSplit,inputToPass.TargetFolderLocation);
break;
case 1:
split.SplitFileBasedOnSize(inputToPass.SourceFileLocation, 
	inputToPass.SizeToSplit, inputToPass.TargetFolderLocation);
break;
case 2:
split.SplitFileBasedOnNumberOfFiles(inputToPass.SourceFileLocation, 
	inputToPass.NumOfFileToSplits, inputToPass.TargetFolderLocation);
break;
default:
break;
}
}
}//end of method

完成时,background worker 将调用 RunWorkerCompleted 来向用户更新通知消息,并重置所有控件。

使用 background worker,我们将调用我们的主要逻辑(JAXE DLL),其中将包含我们的文件分割逻辑。

如果您看到上面的代码片段,我们是这样调用方法的

split.SplitFileBasedOnDuration(inputToPass.SourceFileLocation,
	inputToPass.DurationToSplit,inputToPass.TargetFolderLocation);

我调用了 SplitFileBasedOnDuration 方法,它是我们外部 DLL 中 JAXE 类的部分,用于按时间间隔分割文件。

为了获取输入文件的时长,我使用了开源 DLL,即“DirectShowLib”。要了解更多信息,请访问this link。获取完整时长后,我开始按时间间隔分割文件。

为了按时长分割文件,我根据输入文件大小的比例进行了计算。请看下面的代码。

//Open the file in read mode
var fsDur = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
//variable to store inputs file total length in seconds
var totalDuration = 0.0;
//get the length of file in seconds 
GetVideoLength(inputFile, out totalDuration);

每个字节的时长。

//get the duration of each in interval to split
var durationOfEachFile = Convert.ToDouble(timeDuration);
//get the duration of 1 bytes file
var fractionOfTimeDuration = totalDuration / durationOfEachFile;  

与用户提供的时长相对应的每个字节的文件大小

//get the size corresponding to duration to split the file
var sizeOfEachFile = (int)Math.Ceiling((double)fsDur.Length / fractionOfTimeDuration);

现在我们将获得分割源文件后的总文件数。

//get the total file to split after comparing time interval with size
var numberOfFiles = (int)Math.Ceiling((double)fsDur.Length / sizeOfEachFile);
//the base file name
var baseFileName = Path.GetFileNameWithoutExtension(inputFile);
//get the extension of the file
var extension = Path.GetExtension(inputFile);

最后,我们将开始逐字节读取源文件并将其放入输出文件

for (var i = 1; i <= numberOfFiles; i++)
{
//file name after splitting
var outputFile = new FileStream(outPutLocation + "\\" 
	+ baseFileName + "_" + i.ToString().PadLeft(5, Convert.ToChar("0")) 
	+ extension, FileMode.Create, FileAccess.Write);
var bytesRead = 0;
//array of bytes to hold the piece of file data after splitting
var buffer = new byte[sizeOfEachFile];
//split the file byte by byte
if ((bytesRead = fsDur.Read(buffer, 0, sizeOfEachFile)) > 0)
outputFile.Write(buffer, 0, bytesRead);
outputFile.Close();
}

输出文件的名称格式将是 SourceFilename_Numericvalue.extension.

整个逻辑如下

//Split the file based on duration
//Note: There might be a chances where exact duration of the file 
//would not be same as user provided. deviation of few seconds
public void SplitFileBasedOnDuration(string inputFile, 
		double timeDuration,string outPutLocation)
{
//Open the file in read mode
var fsDur = new FileStream(inputFile, FileMode.Open, FileAccess.Read);
//variable to store inputs file total length in seconds
var totalDuration = 0.0;
//get the length of file in seconds 
GetVideoLength(inputFile, out totalDuration);
//get the duration of each in interval to split
var durationOfEachFile = Convert.ToDouble(timeDuration);
//get the duration of 1 bytes file
var fractionOfTimeDuration = totalDuration / durationOfEachFile; 
//get the size corresponding to duration to split the file
var sizeOfEachFile = (int)Math.Ceiling((double)fsDur.Length / fractionOfTimeDuration);
//get the total file to split after comparing time interval with size
var numberOfFiles = (int)Math.Ceiling((double)fsDur.Length / sizeOfEachFile);
//the base file name
var baseFileName = Path.GetFileNameWithoutExtension(inputFile);
//get the extension of the file
var extension = Path.GetExtension(inputFile);
for (var i = 1; i <= numberOfFiles; i++)
{
//file name after splitting
var outputFile = new FileStream(outPutLocation + 
	"\\" + baseFileName + "_" + i.ToString().PadLeft(5, Convert.ToChar("0")) + 
	extension, FileMode.Create, FileAccess.Write);
var bytesRead = 0;
//array of bytes to holed the piece of file data after splitting
var buffer = new byte[sizeOfEachFile];
//split the file byte by byte
if ((bytesRead = fsDur.Read(buffer, 0, sizeOfEachFile)) > 0)
outputFile.Write(buffer, 0, bytesRead);
outputFile.Close();
}
//close the File stream
fsDur.Close();
//return "Done!";
}

最终,在分割完源文件后,我们的文件就可以使用了。我在几种扩展名的源文件上测试了它,效果很好。但我没有在所有类型的源文件上进行测试。可能有些文件无效。如果您发现任何错误,请告知我。我将很高兴收到大家的反馈。

关注点

这是我第一次使用 background worker 的概念,我发现它在 Windows 应用程序中非常有趣且有用,当你不想让 UI 在执行复杂/耗时的过程时冻结。按时长分割文件也极具挑战性,我克服了一些问题,但输出文件的时长仍然存在一些偏差(几分之一秒)。

注意事项

BackgroundWorker 使用线程池,这意味着您永远不应在 BackgroundWorker 线程上调用Abort

© . All rights reserved.