├── .gitignore
├── LICENSE
├── QiyiFLV2MP4 GUI
├── App.config
├── Library
│ ├── FLVFile.cs
│ ├── General.cs
│ ├── MP4Box.exe
│ ├── WAVFile.cs
│ ├── js.dll
│ ├── js32.dll
│ └── libgpac.dll
├── Main.Designer.cs
├── Main.cs
├── Main.resx
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
└── QiyiFLV2MP4 GUI.csproj
├── QiyiFLV2MP4.sln
├── QiyiFLV2MP4
├── App.config
├── Library
│ ├── FLVFile.cs
│ ├── General.cs
│ ├── MP4Box.exe
│ ├── WAVFile.cs
│ ├── js.dll
│ ├── js32.dll
│ └── libgpac.dll
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
└── QiyiFLV2MP4.csproj
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studo 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | *_i.c
42 | *_p.c
43 | *_i.h
44 | *.ilk
45 | *.meta
46 | *.obj
47 | *.pch
48 | *.pdb
49 | *.pgc
50 | *.pgd
51 | *.rsp
52 | *.sbr
53 | *.tlb
54 | *.tli
55 | *.tlh
56 | *.tmp
57 | *.tmp_proj
58 | *.log
59 | *.vspscc
60 | *.vssscc
61 | .builds
62 | *.pidb
63 | *.svclog
64 | *.scc
65 |
66 | # Chutzpah Test files
67 | _Chutzpah*
68 |
69 | # Visual C++ cache files
70 | ipch/
71 | *.aps
72 | *.ncb
73 | *.opensdf
74 | *.sdf
75 | *.cachefile
76 |
77 | # Visual Studio profiler
78 | *.psess
79 | *.vsp
80 | *.vspx
81 |
82 | # TFS 2012 Local Workspace
83 | $tf/
84 |
85 | # Guidance Automation Toolkit
86 | *.gpState
87 |
88 | # ReSharper is a .NET coding add-in
89 | _ReSharper*/
90 | *.[Rr]e[Ss]harper
91 | *.DotSettings.user
92 |
93 | # JustCode is a .NET coding addin-in
94 | .JustCode
95 |
96 | # TeamCity is a build add-in
97 | _TeamCity*
98 |
99 | # DotCover is a Code Coverage Tool
100 | *.dotCover
101 |
102 | # NCrunch
103 | _NCrunch_*
104 | .*crunch*.local.xml
105 |
106 | # MightyMoose
107 | *.mm.*
108 | AutoTest.Net/
109 |
110 | # Web workbench (sass)
111 | .sass-cache/
112 |
113 | # Installshield output folder
114 | [Ee]xpress/
115 |
116 | # DocProject is a documentation generator add-in
117 | DocProject/buildhelp/
118 | DocProject/Help/*.HxT
119 | DocProject/Help/*.HxC
120 | DocProject/Help/*.hhc
121 | DocProject/Help/*.hhk
122 | DocProject/Help/*.hhp
123 | DocProject/Help/Html2
124 | DocProject/Help/html
125 |
126 | # Click-Once directory
127 | publish/
128 |
129 | # Publish Web Output
130 | *.[Pp]ublish.xml
131 | *.azurePubxml
132 | # TODO: Comment the next line if you want to checkin your web deploy settings
133 | # but database connection strings (with potential passwords) will be unencrypted
134 | *.pubxml
135 | *.publishproj
136 |
137 | # NuGet Packages
138 | *.nupkg
139 | # The packages folder can be ignored because of Package Restore
140 | **/packages/*
141 | # except build/, which is used as an MSBuild target.
142 | !**/packages/build/
143 | # Uncomment if necessary however generally it will be regenerated when needed
144 | #!**/packages/repositories.config
145 |
146 | # Windows Azure Build Output
147 | csx/
148 | *.build.csdef
149 |
150 | # Windows Store app package directory
151 | AppPackages/
152 |
153 | # Others
154 | *.[Cc]ache
155 | ClientBin/
156 | [Ss]tyle[Cc]op.*
157 | ~$*
158 | *~
159 | *.dbmdl
160 | *.dbproj.schemaview
161 | *.pfx
162 | *.publishsettings
163 | node_modules/
164 | bower_components/
165 |
166 | # RIA/Silverlight projects
167 | Generated_Code/
168 |
169 | # Backup & report files from converting an old project file
170 | # to a newer Visual Studio version. Backup files are not needed,
171 | # because we have git ;-)
172 | _UpgradeReport_Files/
173 | Backup*/
174 | UpgradeLog*.XML
175 | UpgradeLog*.htm
176 |
177 | # SQL Server files
178 | *.mdf
179 | *.ldf
180 |
181 | # Business Intelligence projects
182 | *.rdl.data
183 | *.bim.layout
184 | *.bim_*.settings
185 |
186 | # Microsoft Fakes
187 | FakesAssemblies/
188 |
189 | # Node.js Tools for Visual Studio
190 | .ntvs_analysis.dat
191 |
192 | # Visual Studio 6 build log
193 | *.plg
194 |
195 | # Visual Studio 6 workspace options file
196 | *.opt
197 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "{}" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright 2016 风漠兮
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/FLVFile.cs:
--------------------------------------------------------------------------------
1 | // ****************************************************************************
2 | //
3 | // FLV Extract
4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com)
5 | //
6 | // This program is free software; you can redistribute it and/or modify
7 | // it under the terms of the GNU General Public License as published by
8 | // the Free Software Foundation; either version 2 of the License, or
9 | // (at your option) any later version.
10 | //
11 | // This program is distributed in the hope that it will be useful,
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | // GNU General Public License for more details.
15 | //
16 | // You should have received a copy of the GNU General Public License
17 | // along with this program; if not, write to the Free Software
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | //
20 | // ****************************************************************************
21 |
22 | using System;
23 | using System.Collections.Generic;
24 | using System.IO;
25 | using System.Text;
26 |
27 | namespace QiyiFLV2MP4_GUI
28 | {
29 | interface IAudioWriter {
30 | void WriteChunk(byte[] chunk, uint timeStamp);
31 | void Finish();
32 | string Path { get; }
33 | }
34 |
35 | interface IVideoWriter {
36 | void WriteChunk(byte[] chunk, uint timeStamp, int frameType);
37 | void Finish(FractionUInt32 averageFrameRate);
38 | string Path { get; }
39 | }
40 |
41 | public delegate bool OverwriteDelegate(string destPath);
42 |
43 | public class FLVFile : IDisposable {
44 | static readonly string[] _outputExtensions = new string[] { ".avi", ".mp3", ".264", ".aac", ".spx", ".txt" };
45 |
46 | string _inputPath, _outputDirectory, _outputPathBase;
47 | OverwriteDelegate _overwrite;
48 | FileStream _fs;
49 | long _fileOffset, _fileLength;
50 | IAudioWriter _audioWriter;
51 | IVideoWriter _videoWriter;
52 | TimeCodeWriter _timeCodeWriter;
53 | List _videoTimeStamps;
54 | bool _extractAudio, _extractVideo, _extractTimeCodes;
55 | bool _extractedAudio, _extractedVideo, _extractedTimeCodes;
56 | FractionUInt32? _averageFrameRate, _trueFrameRate;
57 | List _warnings;
58 |
59 | public FLVFile(string path) {
60 | _inputPath = path;
61 | _outputDirectory = Path.GetDirectoryName(path);
62 | _warnings = new List();
63 | _fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536);
64 | _fileOffset = 0;
65 | _fileLength = _fs.Length;
66 | }
67 |
68 | public void Dispose() {
69 | if (_fs != null) {
70 | _fs.Close();
71 | _fs = null;
72 | }
73 | CloseOutput(null, true);
74 | }
75 |
76 | public void Close() {
77 | Dispose();
78 | }
79 |
80 | public string OutputDirectory {
81 | get { return _outputDirectory; }
82 | set { _outputDirectory = value; }
83 | }
84 |
85 | public FractionUInt32? AverageFrameRate {
86 | get { return _averageFrameRate; }
87 | }
88 |
89 | public FractionUInt32? TrueFrameRate {
90 | get { return _trueFrameRate; }
91 | }
92 |
93 | public string[] Warnings {
94 | get { return _warnings.ToArray(); }
95 | }
96 |
97 | public bool ExtractedAudio {
98 | get { return _extractedAudio; }
99 | }
100 |
101 | public bool ExtractedVideo {
102 | get { return _extractedVideo; }
103 | }
104 |
105 | public bool ExtractedTimeCodes {
106 | get { return _extractedTimeCodes; }
107 | }
108 |
109 | public void ExtractStreams(bool extractAudio, bool extractVideo, bool extractTimeCodes, OverwriteDelegate overwrite) {
110 | uint dataOffset, flags, prevTagSize;
111 |
112 | _outputPathBase = Path.Combine(_outputDirectory, Path.GetFileNameWithoutExtension(_inputPath));
113 | _overwrite = overwrite;
114 | _extractAudio = extractAudio;
115 | _extractVideo = extractVideo;
116 | _extractTimeCodes = extractTimeCodes;
117 | _videoTimeStamps = new List();
118 |
119 | Seek(0);
120 | if (_fileLength < 4 || ReadUInt32() != 0x464C5601) {
121 | if (_fileLength >= 8 && ReadUInt32() == 0x66747970) {
122 | throw new Exception("This is a MP4 file. Please input a FLV file!");
123 | }
124 | else {
125 | throw new Exception("This isn't a FLV file. Please input a FLV file!");
126 | }
127 | }
128 |
129 | if (Array.IndexOf(_outputExtensions, Path.GetExtension(_inputPath).ToLowerInvariant()) != -1) {
130 | // Can't have the same extension as files we output
131 | throw new Exception("Please change the extension of this FLV file.");
132 | }
133 |
134 | if (!Directory.Exists(_outputDirectory)) {
135 | throw new Exception("Output directory doesn't exist.");
136 | }
137 |
138 | flags = ReadUInt8();
139 | dataOffset = ReadUInt32();
140 |
141 | Seek(dataOffset);
142 |
143 | prevTagSize = ReadUInt32();
144 | while (_fileOffset < _fileLength) {
145 | if (!ReadTag()) break;
146 | if ((_fileLength - _fileOffset) < 4) break;
147 | prevTagSize = ReadUInt32();
148 | }
149 |
150 | _averageFrameRate = CalculateAverageFrameRate();
151 | _trueFrameRate = CalculateTrueFrameRate();
152 |
153 | CloseOutput(_averageFrameRate, false);
154 | }
155 |
156 | private void CloseOutput(FractionUInt32? averageFrameRate, bool disposing) {
157 | if (_videoWriter != null) {
158 | _videoWriter.Finish(averageFrameRate ?? new FractionUInt32(25, 1));
159 | if (disposing && (_videoWriter.Path != null)) {
160 | try { File.Delete(_videoWriter.Path); }
161 | catch { }
162 | }
163 | _videoWriter = null;
164 | }
165 | if (_audioWriter != null) {
166 | _audioWriter.Finish();
167 | if (disposing && (_audioWriter.Path != null)) {
168 | try { File.Delete(_audioWriter.Path); }
169 | catch { }
170 | }
171 | _audioWriter = null;
172 | }
173 | if (_timeCodeWriter != null) {
174 | _timeCodeWriter.Finish();
175 | if (disposing && (_timeCodeWriter.Path != null)) {
176 | try { File.Delete(_timeCodeWriter.Path); }
177 | catch { }
178 | }
179 | _timeCodeWriter = null;
180 | }
181 | }
182 |
183 | private bool ReadTag() {
184 | uint tagType, dataSize, timeStamp, streamID, mediaInfo;
185 | byte[] data;
186 |
187 | if ((_fileLength - _fileOffset) < 11) {
188 | return false;
189 | }
190 |
191 | // Read tag header
192 | tagType = ReadUInt8();
193 | dataSize = ReadUInt24();
194 | timeStamp = ReadUInt24();
195 | timeStamp |= ReadUInt8() << 24;
196 | streamID = ReadUInt24();
197 |
198 | // Read tag data
199 | if (dataSize == 0) {
200 | return true;
201 | }
202 | if ((_fileLength - _fileOffset) < dataSize) {
203 | return false;
204 | }
205 | mediaInfo = ReadUInt8();
206 | dataSize -= 1;
207 | data = ReadBytes((int)dataSize);
208 |
209 | if (tagType == 0x8) { // Audio
210 | if (_audioWriter == null) {
211 | _audioWriter = _extractAudio ? GetAudioWriter(mediaInfo) : new DummyAudioWriter();
212 | _extractedAudio = !(_audioWriter is DummyAudioWriter);
213 | }
214 | _audioWriter.WriteChunk(data, timeStamp);
215 | }
216 | else if ((tagType == 0x9) && ((mediaInfo >> 4) != 5)) { // Video
217 | if (_videoWriter == null) {
218 | _videoWriter = _extractVideo ? GetVideoWriter(mediaInfo) : new DummyVideoWriter();
219 | _extractedVideo = !(_videoWriter is DummyVideoWriter);
220 | }
221 | if (_timeCodeWriter == null) {
222 | string path = _outputPathBase + ".txt";
223 | _timeCodeWriter = new TimeCodeWriter((_extractTimeCodes && CanWriteTo(path)) ? path : null);
224 | _extractedTimeCodes = _extractTimeCodes;
225 | }
226 | _videoTimeStamps.Add(timeStamp);
227 | _videoWriter.WriteChunk(data, timeStamp, (int)((mediaInfo & 0xF0) >> 4));
228 | _timeCodeWriter.Write(timeStamp);
229 | }
230 |
231 | return true;
232 | }
233 |
234 | private IAudioWriter GetAudioWriter(uint mediaInfo) {
235 | uint format = mediaInfo >> 4;
236 | uint rate = (mediaInfo >> 2) & 0x3;
237 | uint bits = (mediaInfo >> 1) & 0x1;
238 | uint chans = mediaInfo & 0x1;
239 | string path;
240 |
241 | if ((format == 2) || (format == 14)) { // MP3
242 | path = _outputPathBase + ".mp3";
243 | if (!CanWriteTo(path)) return new DummyAudioWriter();
244 | return new MP3Writer(path, _warnings);
245 | }
246 | else if ((format == 0) || (format == 3)) { // PCM
247 | int sampleRate = 0;
248 | switch (rate) {
249 | case 0: sampleRate = 5512; break;
250 | case 1: sampleRate = 11025; break;
251 | case 2: sampleRate = 22050; break;
252 | case 3: sampleRate = 44100; break;
253 | }
254 | path = _outputPathBase + ".wav";
255 | if (!CanWriteTo(path)) return new DummyAudioWriter();
256 | if (format == 0) {
257 | _warnings.Add("PCM byte order unspecified, assuming little endian.");
258 | }
259 | return new WAVWriter(path, (bits == 1) ? 16 : 8,
260 | (chans == 1) ? 2 : 1, sampleRate);
261 | }
262 | else if (format == 10) { // AAC
263 | path = _outputPathBase + ".aac";
264 | if (!CanWriteTo(path)) return new DummyAudioWriter();
265 | return new AACWriter(path);
266 | }
267 | else if (format == 11) { // Speex
268 | path = _outputPathBase + ".spx";
269 | if (!CanWriteTo(path)) return new DummyAudioWriter();
270 | return new SpeexWriter(path, (int)(_fileLength & 0xFFFFFFFF));
271 | }
272 | else {
273 | string typeStr;
274 |
275 | if (format == 1)
276 | typeStr = "ADPCM";
277 | else if ((format == 4) || (format == 5) || (format == 6))
278 | typeStr = "Nellymoser";
279 | else
280 | typeStr = "format=" + format.ToString();
281 |
282 | _warnings.Add("Unable to extract audio (" + typeStr + " is unsupported).");
283 |
284 | return new DummyAudioWriter();
285 | }
286 | }
287 |
288 | private IVideoWriter GetVideoWriter(uint mediaInfo) {
289 | uint codecID = mediaInfo & 0x0F;
290 | string path;
291 |
292 | if ((codecID == 2) || (codecID == 4) || (codecID == 5)) {
293 | path = _outputPathBase + ".avi";
294 | if (!CanWriteTo(path)) return new DummyVideoWriter();
295 | return new AVIWriter(path, (int)codecID, _warnings);
296 | }
297 | else if (codecID == 7) {
298 | path = _outputPathBase + ".264";
299 | if (!CanWriteTo(path)) return new DummyVideoWriter();
300 | return new RawH264Writer(path);
301 | }
302 | else {
303 | string typeStr;
304 |
305 | if (codecID == 3)
306 | typeStr = "Screen";
307 | else if (codecID == 6)
308 | typeStr = "Screen2";
309 | else
310 | typeStr = "codecID=" + codecID.ToString();
311 |
312 | _warnings.Add("Unable to extract video (" + typeStr + " is unsupported).");
313 |
314 | return new DummyVideoWriter();
315 | }
316 | }
317 |
318 | private bool CanWriteTo(string path) {
319 | if (File.Exists(path) && (_overwrite != null)) {
320 | return _overwrite(path);
321 | }
322 | return true;
323 | }
324 |
325 | private FractionUInt32? CalculateAverageFrameRate() {
326 | FractionUInt32 frameRate;
327 | int frameCount = _videoTimeStamps.Count;
328 |
329 | if (frameCount > 1) {
330 | frameRate.N = (uint)(frameCount - 1) * 1000;
331 | frameRate.D = _videoTimeStamps[frameCount - 1] - _videoTimeStamps[0];
332 | frameRate.Reduce();
333 | return frameRate;
334 | }
335 | else {
336 | return null;
337 | }
338 | }
339 |
340 | private FractionUInt32? CalculateTrueFrameRate() {
341 | FractionUInt32 frameRate;
342 | Dictionary deltaCount = new Dictionary();
343 | int i, threshold;
344 | uint delta, count, minDelta;
345 |
346 | // Calculate the distance between the timestamps, count how many times each delta appears
347 | for (i = 1; i < _videoTimeStamps.Count; i++) {
348 | int deltaS = (int)((long)_videoTimeStamps[i] - (long)_videoTimeStamps[i - 1]);
349 |
350 | if (deltaS <= 0) continue;
351 | delta = (uint)deltaS;
352 |
353 | if (deltaCount.ContainsKey(delta)) {
354 | deltaCount[delta] += 1;
355 | }
356 | else {
357 | deltaCount.Add(delta, 1);
358 | }
359 | }
360 |
361 | threshold = _videoTimeStamps.Count / 10;
362 | minDelta = UInt32.MaxValue;
363 |
364 | // Find the smallest delta that made up at least 10% of the frames (grouping in delta+1
365 | // because of rounding, e.g. a NTSC video will have deltas of 33 and 34 ms)
366 | foreach (KeyValuePair deltaItem in deltaCount) {
367 | delta = deltaItem.Key;
368 | count = deltaItem.Value;
369 |
370 | if (deltaCount.ContainsKey(delta + 1)) {
371 | count += deltaCount[delta + 1];
372 | }
373 |
374 | if ((count >= threshold) && (delta < minDelta)) {
375 | minDelta = delta;
376 | }
377 | }
378 |
379 | // Calculate the frame rate based on the smallest delta, and delta+1 if present
380 | if (minDelta != UInt32.MaxValue) {
381 | uint totalTime, totalFrames;
382 |
383 | count = deltaCount[minDelta];
384 | totalTime = minDelta * count;
385 | totalFrames = count;
386 |
387 | if (deltaCount.ContainsKey(minDelta + 1)) {
388 | count = deltaCount[minDelta + 1];
389 | totalTime += (minDelta + 1) * count;
390 | totalFrames += count;
391 | }
392 |
393 | if (totalTime != 0) {
394 | frameRate.N = totalFrames * 1000;
395 | frameRate.D = totalTime;
396 | frameRate.Reduce();
397 | return frameRate;
398 | }
399 | }
400 |
401 | // Unable to calculate frame rate
402 | return null;
403 | }
404 |
405 | private void Seek(long offset) {
406 | _fs.Seek(offset, SeekOrigin.Begin);
407 | _fileOffset = offset;
408 | }
409 |
410 | private uint ReadUInt8() {
411 | _fileOffset += 1;
412 | return (uint)_fs.ReadByte();
413 | }
414 |
415 | private uint ReadUInt24() {
416 | byte[] x = new byte[4];
417 | _fs.Read(x, 1, 3);
418 | _fileOffset += 3;
419 | return BitConverterBE.ToUInt32(x, 0);
420 | }
421 |
422 | private uint ReadUInt32() {
423 | byte[] x = new byte[4];
424 | _fs.Read(x, 0, 4);
425 | _fileOffset += 4;
426 | return BitConverterBE.ToUInt32(x, 0);
427 | }
428 |
429 | private byte[] ReadBytes(int length) {
430 | byte[] buff = new byte[length];
431 | _fs.Read(buff, 0, length);
432 | _fileOffset += length;
433 | return buff;
434 | }
435 | }
436 |
437 | class DummyAudioWriter : IAudioWriter {
438 | public DummyAudioWriter() {
439 | }
440 |
441 | public void WriteChunk(byte[] chunk, uint timeStamp) {
442 | }
443 |
444 | public void Finish() {
445 | }
446 |
447 | public string Path {
448 | get {
449 | return null;
450 | }
451 | }
452 | }
453 |
454 | class DummyVideoWriter : IVideoWriter {
455 | public DummyVideoWriter() {
456 | }
457 |
458 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) {
459 | }
460 |
461 | public void Finish(FractionUInt32 averageFrameRate) {
462 | }
463 |
464 | public string Path {
465 | get {
466 | return null;
467 | }
468 | }
469 | }
470 |
471 | class MP3Writer : IAudioWriter {
472 | string _path;
473 | FileStream _fs;
474 | List _warnings;
475 | List _chunkBuffer;
476 | List _frameOffsets;
477 | uint _totalFrameLength;
478 | bool _isVBR;
479 | bool _delayWrite;
480 | bool _hasVBRHeader;
481 | bool _writeVBRHeader;
482 | int _firstBitRate;
483 | int _mpegVersion;
484 | int _sampleRate;
485 | int _channelMode;
486 | uint _firstFrameHeader;
487 |
488 | public MP3Writer(string path, List warnings) {
489 | _path = path;
490 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
491 | _warnings = warnings;
492 | _chunkBuffer = new List();
493 | _frameOffsets = new List();
494 | _delayWrite = true;
495 | }
496 |
497 | public void WriteChunk(byte[] chunk, uint timeStamp) {
498 | _chunkBuffer.Add(chunk);
499 | ParseMP3Frames(chunk);
500 | if (_delayWrite && _totalFrameLength >= 65536) {
501 | _delayWrite = false;
502 | }
503 | if (!_delayWrite) {
504 | Flush();
505 | }
506 | }
507 |
508 | public void Finish() {
509 | Flush();
510 | if (_writeVBRHeader) {
511 | _fs.Seek(0, SeekOrigin.Begin);
512 | WriteVBRHeader(false);
513 | }
514 | _fs.Close();
515 | }
516 |
517 | public string Path {
518 | get {
519 | return _path;
520 | }
521 | }
522 |
523 | private void Flush() {
524 | foreach (byte[] chunk in _chunkBuffer) {
525 | _fs.Write(chunk, 0, chunk.Length);
526 | }
527 | _chunkBuffer.Clear();
528 | }
529 |
530 | private void ParseMP3Frames(byte[] buff) {
531 | int[] MPEG1BitRate = new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 };
532 | int[] MPEG2XBitRate = new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 };
533 | int[] MPEG1SampleRate = new int[] { 44100, 48000, 32000, 0 };
534 | int[] MPEG20SampleRate = new int[] { 22050, 24000, 16000, 0 };
535 | int[] MPEG25SampleRate = new int[] { 11025, 12000, 8000, 0 };
536 |
537 | int offset = 0;
538 | int length = buff.Length;
539 |
540 | while (length >= 4) {
541 | ulong header;
542 | int mpegVersion, layer, bitRate, sampleRate, padding, channelMode;
543 | int frameLen;
544 |
545 | header = (ulong)BitConverterBE.ToUInt32(buff, offset) << 32;
546 | if (BitHelper.Read(ref header, 11) != 0x7FF) {
547 | break;
548 | }
549 | mpegVersion = BitHelper.Read(ref header, 2);
550 | layer = BitHelper.Read(ref header, 2);
551 | BitHelper.Read(ref header, 1);
552 | bitRate = BitHelper.Read(ref header, 4);
553 | sampleRate = BitHelper.Read(ref header, 2);
554 | padding = BitHelper.Read(ref header, 1);
555 | BitHelper.Read(ref header, 1);
556 | channelMode = BitHelper.Read(ref header, 2);
557 |
558 | if ((mpegVersion == 1) || (layer != 1) || (bitRate == 0) || (bitRate == 15) || (sampleRate == 3)) {
559 | break;
560 | }
561 |
562 | bitRate = ((mpegVersion == 3) ? MPEG1BitRate[bitRate] : MPEG2XBitRate[bitRate]) * 1000;
563 |
564 | if (mpegVersion == 3)
565 | sampleRate = MPEG1SampleRate[sampleRate];
566 | else if (mpegVersion == 2)
567 | sampleRate = MPEG20SampleRate[sampleRate];
568 | else
569 | sampleRate = MPEG25SampleRate[sampleRate];
570 |
571 | frameLen = GetFrameLength(mpegVersion, bitRate, sampleRate, padding);
572 | if (frameLen > length) {
573 | break;
574 | }
575 |
576 | bool isVBRHeaderFrame = false;
577 | if (_frameOffsets.Count == 0) {
578 | // Check for an existing VBR header just to be safe (I haven't seen any in FLVs)
579 | int o = offset + GetFrameDataOffset(mpegVersion, channelMode);
580 | if (BitConverterBE.ToUInt32(buff, o) == 0x58696E67) { // "Xing"
581 | isVBRHeaderFrame = true;
582 | _delayWrite = false;
583 | _hasVBRHeader = true;
584 | }
585 | }
586 |
587 | if (isVBRHeaderFrame) { }
588 | else if (_firstBitRate == 0) {
589 | _firstBitRate = bitRate;
590 | _mpegVersion = mpegVersion;
591 | _sampleRate = sampleRate;
592 | _channelMode = channelMode;
593 | _firstFrameHeader = BitConverterBE.ToUInt32(buff, offset);
594 | }
595 | else if (!_isVBR && (bitRate != _firstBitRate)) {
596 | _isVBR = true;
597 | if (_hasVBRHeader) { }
598 | else if (_delayWrite) {
599 | WriteVBRHeader(true);
600 | _writeVBRHeader = true;
601 | _delayWrite = false;
602 | }
603 | else {
604 | _warnings.Add("Detected VBR too late, cannot add VBR header.");
605 | }
606 | }
607 |
608 | _frameOffsets.Add(_totalFrameLength + (uint)offset);
609 |
610 | offset += frameLen;
611 | length -= frameLen;
612 | }
613 |
614 | _totalFrameLength += (uint)buff.Length;
615 | }
616 |
617 | private void WriteVBRHeader(bool isPlaceholder) {
618 | byte[] buff = new byte[GetFrameLength(_mpegVersion, 64000, _sampleRate, 0)];
619 | if (!isPlaceholder) {
620 | uint header = _firstFrameHeader;
621 | int dataOffset = GetFrameDataOffset(_mpegVersion, _channelMode);
622 | header &= 0xFFFF0DFF; // Clear bitrate and padding fields
623 | header |= 0x00010000; // Set protection bit (indicates that CRC is NOT present)
624 | header |= (uint)((_mpegVersion == 3) ? 5 : 8) << 12; // 64 kbit/sec
625 | General.CopyBytes(buff, 0, BitConverterBE.GetBytes(header));
626 | General.CopyBytes(buff, dataOffset, BitConverterBE.GetBytes((uint)0x58696E67)); // "Xing"
627 | General.CopyBytes(buff, dataOffset + 4, BitConverterBE.GetBytes((uint)0x7)); // Flags
628 | General.CopyBytes(buff, dataOffset + 8, BitConverterBE.GetBytes((uint)_frameOffsets.Count)); // Frame count
629 | General.CopyBytes(buff, dataOffset + 12, BitConverterBE.GetBytes((uint)_totalFrameLength)); // File length
630 | for (int i = 0; i < 100; i++) {
631 | int frameIndex = (int)((i / 100.0) * _frameOffsets.Count);
632 | buff[dataOffset + 16 + i] = (byte)((_frameOffsets[frameIndex] / (double)_totalFrameLength) * 256.0);
633 | }
634 | }
635 | _fs.Write(buff, 0, buff.Length);
636 | }
637 |
638 | private int GetFrameLength(int mpegVersion, int bitRate, int sampleRate, int padding) {
639 | return ((mpegVersion == 3) ? 144 : 72) * bitRate / sampleRate + padding;
640 | }
641 |
642 | private int GetFrameDataOffset(int mpegVersion, int channelMode) {
643 | return 4 + ((mpegVersion == 3) ?
644 | ((channelMode == 3) ? 17 : 32) :
645 | ((channelMode == 3) ? 9 : 17));
646 | }
647 | }
648 |
649 | class SpeexWriter : IAudioWriter {
650 | const string _vendorString = "FLV Extract";
651 | const uint _sampleRate = 16000;
652 | const uint _msPerFrame = 20;
653 | const uint _samplesPerFrame = _sampleRate / (1000 / _msPerFrame);
654 | const int _targetPageDataSize = 4096;
655 |
656 | string _path;
657 | FileStream _fs;
658 | int _serialNumber;
659 | List _packetList;
660 | int _packetListDataSize;
661 | byte[] _pageBuff;
662 | int _pageBuffOffset;
663 | uint _pageSequenceNumber;
664 | ulong _granulePosition;
665 |
666 | public SpeexWriter(string path, int serialNumber) {
667 | _path = path;
668 | _serialNumber = serialNumber;
669 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
670 | _fs.Seek((28 + 80) + (28 + 8 + _vendorString.Length), SeekOrigin.Begin); // Speex header + Vorbis comment
671 | _packetList = new List();
672 | _packetListDataSize = 0;
673 | _pageBuff = new byte[27 + 255 + _targetPageDataSize + 254]; // Header + max segment table + target data size + extra segment
674 | _pageBuffOffset = 0;
675 | _pageSequenceNumber = 2; // First audio packet
676 | _granulePosition = 0;
677 | }
678 |
679 | public void WriteChunk(byte[] chunk, uint timeStamp) {
680 | int[] subModeSizes = new int[] { 0, 43, 119, 160, 220, 300, 364, 492, 79 };
681 | int[] wideBandSizes = new int[] { 0, 36, 112, 192, 352 };
682 | int[] inBandSignalSizes = new int[] { 1, 1, 4, 4, 4, 4, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 };
683 | int frameStart = -1;
684 | int frameEnd = 0;
685 | int offset = 0;
686 | int length = chunk.Length * 8;
687 | int x;
688 |
689 | while (length - offset >= 5) {
690 | x = BitHelper.Read(chunk, ref offset, 1);
691 | if (x != 0) {
692 | // wideband frame
693 | x = BitHelper.Read(chunk, ref offset, 3);
694 | if (x < 1 || x > 4) goto Error;
695 | offset += wideBandSizes[x] - 4;
696 | }
697 | else {
698 | x = BitHelper.Read(chunk, ref offset, 4);
699 | if (x >= 1 && x <= 8) {
700 | // narrowband frame
701 | if (frameStart != -1) {
702 | WriteFramePacket(chunk, frameStart, frameEnd);
703 | }
704 | frameStart = frameEnd;
705 | offset += subModeSizes[x] - 5;
706 | }
707 | else if (x == 15) {
708 | // terminator
709 | break;
710 | }
711 | else if (x == 14) {
712 | // in-band signal
713 | if (length - offset < 4) goto Error;
714 | x = BitHelper.Read(chunk, ref offset, 4);
715 | offset += inBandSignalSizes[x];
716 | }
717 | else if (x == 13) {
718 | // custom in-band signal
719 | if (length - offset < 5) goto Error;
720 | x = BitHelper.Read(chunk, ref offset, 5);
721 | offset += x * 8;
722 | }
723 | else goto Error;
724 | }
725 | frameEnd = offset;
726 | }
727 | if (offset > length) goto Error;
728 |
729 | if (frameStart != -1) {
730 | WriteFramePacket(chunk, frameStart, frameEnd);
731 | }
732 |
733 | return;
734 |
735 | Error:
736 | throw new Exception("Invalid Speex data.");
737 | }
738 |
739 | public void Finish() {
740 | WritePage();
741 | FlushPage(true);
742 | _fs.Seek(0, SeekOrigin.Begin);
743 | _pageSequenceNumber = 0;
744 | _granulePosition = 0;
745 | WriteSpeexHeaderPacket();
746 | WriteVorbisCommentPacket();
747 | FlushPage(false);
748 | _fs.Close();
749 | }
750 |
751 | public string Path {
752 | get {
753 | return _path;
754 | }
755 | }
756 |
757 | private void WriteFramePacket(byte[] data, int startBit, int endBit) {
758 | int lengthBits = endBit - startBit;
759 | byte[] frame = BitHelper.CopyBlock(data, startBit, lengthBits);
760 | if (lengthBits % 8 != 0) {
761 | frame[frame.Length - 1] |= (byte)(0xFF >> ((lengthBits % 8) + 1)); // padding
762 | }
763 | AddPacket(frame, _samplesPerFrame, true);
764 | }
765 |
766 | private void WriteSpeexHeaderPacket() {
767 | byte[] data = new byte[80];
768 | General.CopyBytes(data, 0, Encoding.ASCII.GetBytes("Speex ")); // speex_string
769 | General.CopyBytes(data, 8, Encoding.ASCII.GetBytes("unknown")); // speex_version
770 | data[28] = 1; // speex_version_id
771 | data[32] = 80; // header_size
772 | General.CopyBytes(data, 36, BitConverterLE.GetBytes((uint)_sampleRate)); // rate
773 | data[40] = 1; // mode (e.g. narrowband, wideband)
774 | data[44] = 4; // mode_bitstream_version
775 | data[48] = 1; // nb_channels
776 | General.CopyBytes(data, 52, BitConverterLE.GetBytes(unchecked((uint)-1))); // bitrate
777 | General.CopyBytes(data, 56, BitConverterLE.GetBytes((uint)_samplesPerFrame)); // frame_size
778 | data[60] = 0; // vbr
779 | data[64] = 1; // frames_per_packet
780 | AddPacket(data, 0, false);
781 | }
782 |
783 | private void WriteVorbisCommentPacket() {
784 | byte[] vendorStringBytes = Encoding.ASCII.GetBytes(_vendorString);
785 | byte[] data = new byte[8 + vendorStringBytes.Length];
786 | data[0] = (byte)vendorStringBytes.Length;
787 | General.CopyBytes(data, 4, vendorStringBytes);
788 | AddPacket(data, 0, false);
789 | }
790 |
791 | private void AddPacket(byte[] data, uint sampleLength, bool delayWrite) {
792 | OggPacket packet = new OggPacket();
793 | if (data.Length >= 255) {
794 | throw new Exception("Packet exceeds maximum size.");
795 | }
796 | _granulePosition += sampleLength;
797 | packet.Data = data;
798 | packet.GranulePosition = _granulePosition;
799 | _packetList.Add(packet);
800 | _packetListDataSize += data.Length;
801 | if (!delayWrite || (_packetListDataSize >= _targetPageDataSize) || (_packetList.Count == 255)) {
802 | WritePage();
803 | }
804 | }
805 |
806 | private void WritePage() {
807 | if (_packetList.Count == 0) return;
808 | FlushPage(false);
809 | WriteToPage(BitConverterBE.GetBytes(0x4F676753U), 0, 4); // "OggS"
810 | WriteToPage((byte)0); // Stream structure version
811 | WriteToPage((byte)((_pageSequenceNumber == 0) ? 0x02 : 0)); // Page flags
812 | WriteToPage((ulong)_packetList[_packetList.Count - 1].GranulePosition); // Position in samples
813 | WriteToPage((uint)_serialNumber); // Stream serial number
814 | WriteToPage((uint)_pageSequenceNumber); // Page sequence number
815 | WriteToPage((uint)0); // Checksum
816 | WriteToPage((byte)_packetList.Count); // Page segment count
817 | foreach (OggPacket packet in _packetList) {
818 | WriteToPage((byte)packet.Data.Length);
819 | }
820 | foreach (OggPacket packet in _packetList) {
821 | WriteToPage(packet.Data, 0, packet.Data.Length);
822 | }
823 | _packetList.Clear();
824 | _packetListDataSize = 0;
825 | _pageSequenceNumber++;
826 | }
827 |
828 | private void FlushPage(bool isLastPage) {
829 | if (_pageBuffOffset == 0) return;
830 | if (isLastPage) _pageBuff[5] |= 0x04;
831 | uint crc = OggCRC.Calculate(_pageBuff, 0, _pageBuffOffset);
832 | General.CopyBytes(_pageBuff, 22, BitConverterLE.GetBytes(crc));
833 | _fs.Write(_pageBuff, 0, _pageBuffOffset);
834 | _pageBuffOffset = 0;
835 | }
836 |
837 | private void WriteToPage(byte[] data, int offset, int length) {
838 | Buffer.BlockCopy(data, offset, _pageBuff, _pageBuffOffset, length);
839 | _pageBuffOffset += length;
840 | }
841 |
842 | private void WriteToPage(byte data) {
843 | WriteToPage(new byte[] { data }, 0, 1);
844 | }
845 |
846 | private void WriteToPage(uint data) {
847 | WriteToPage(BitConverterLE.GetBytes(data), 0, 4);
848 | }
849 |
850 | private void WriteToPage(ulong data) {
851 | WriteToPage(BitConverterLE.GetBytes(data), 0, 8);
852 | }
853 |
854 | class OggPacket {
855 | public ulong GranulePosition;
856 | public byte[] Data;
857 | }
858 | }
859 |
860 | class AACWriter : IAudioWriter {
861 | string _path;
862 | FileStream _fs;
863 | int _aacProfile;
864 | int _sampleRateIndex;
865 | int _channelConfig;
866 |
867 | public AACWriter(string path) {
868 | _path = path;
869 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
870 | }
871 |
872 | public void WriteChunk(byte[] chunk, uint timeStamp) {
873 | if (chunk.Length < 1) return;
874 |
875 | if (chunk[0] == 0) { // Header
876 | if (chunk.Length < 3) return;
877 |
878 | ulong bits = (ulong)BitConverterBE.ToUInt16(chunk, 1) << 48;
879 |
880 | _aacProfile = BitHelper.Read(ref bits, 5) - 1;
881 | _sampleRateIndex = BitHelper.Read(ref bits, 4);
882 | _channelConfig = BitHelper.Read(ref bits, 4);
883 |
884 | if ((_aacProfile < 0) || (_aacProfile > 3))
885 | throw new Exception("Unsupported AAC profile.");
886 | if (_sampleRateIndex > 12)
887 | throw new Exception("Invalid AAC sample rate index.");
888 | if (_channelConfig > 6)
889 | throw new Exception("Invalid AAC channel configuration.");
890 | }
891 | else { // Audio data
892 | int dataSize = chunk.Length - 1;
893 | ulong bits = 0;
894 |
895 | // Reference: WriteADTSHeader from FAAC's bitstream.c
896 |
897 | BitHelper.Write(ref bits, 12, 0xFFF);
898 | BitHelper.Write(ref bits, 1, 0);
899 | BitHelper.Write(ref bits, 2, 0);
900 | BitHelper.Write(ref bits, 1, 1);
901 | BitHelper.Write(ref bits, 2, _aacProfile);
902 | BitHelper.Write(ref bits, 4, _sampleRateIndex);
903 | BitHelper.Write(ref bits, 1, 0);
904 | BitHelper.Write(ref bits, 3, _channelConfig);
905 | BitHelper.Write(ref bits, 1, 0);
906 | BitHelper.Write(ref bits, 1, 0);
907 | BitHelper.Write(ref bits, 1, 0);
908 | BitHelper.Write(ref bits, 1, 0);
909 | BitHelper.Write(ref bits, 13, 7 + dataSize);
910 | BitHelper.Write(ref bits, 11, 0x7FF);
911 | BitHelper.Write(ref bits, 2, 0);
912 |
913 | _fs.Write(BitConverterBE.GetBytes(bits), 1, 7);
914 | _fs.Write(chunk, 1, dataSize);
915 | }
916 | }
917 |
918 | public void Finish() {
919 | _fs.Close();
920 | }
921 |
922 | public string Path {
923 | get {
924 | return _path;
925 | }
926 | }
927 | }
928 |
929 | class RawH264Writer : IVideoWriter {
930 | static readonly byte[] _startCode = new byte[] { 0, 0, 0, 1 };
931 |
932 | string _path;
933 | FileStream _fs;
934 | int _nalLengthSize;
935 |
936 | public RawH264Writer(string path) {
937 | _path = path;
938 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
939 | }
940 |
941 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) {
942 | if (chunk.Length < 4) return;
943 |
944 | // Reference: decode_frame from libavcodec's h264.c
945 |
946 | if (chunk[0] == 0) { // Headers
947 | if (chunk.Length < 10) return;
948 |
949 | int offset, spsCount, ppsCount;
950 |
951 | offset = 8;
952 | _nalLengthSize = (chunk[offset++] & 0x03) + 1;
953 | spsCount = chunk[offset++] & 0x1F;
954 | ppsCount = -1;
955 |
956 | while (offset <= chunk.Length - 2) {
957 | if ((spsCount == 0) && (ppsCount == -1)) {
958 | ppsCount = chunk[offset++];
959 | continue;
960 | }
961 |
962 | if (spsCount > 0) spsCount--;
963 | else if (ppsCount > 0) ppsCount--;
964 | else break;
965 |
966 | int len = (int)BitConverterBE.ToUInt16(chunk, offset);
967 | offset += 2;
968 | if (offset + len > chunk.Length) break;
969 | _fs.Write(_startCode, 0, _startCode.Length);
970 | _fs.Write(chunk, offset, len);
971 | offset += len;
972 | }
973 | }
974 | else { // Video data
975 | int offset = 4;
976 |
977 | if (_nalLengthSize != 2) {
978 | _nalLengthSize = 4;
979 | }
980 |
981 | while (offset <= chunk.Length - _nalLengthSize) {
982 | int len = (_nalLengthSize == 2) ?
983 | (int)BitConverterBE.ToUInt16(chunk, offset) :
984 | (int)BitConverterBE.ToUInt32(chunk, offset);
985 | offset += _nalLengthSize;
986 | if (offset + len > chunk.Length) break;
987 | _fs.Write(_startCode, 0, _startCode.Length);
988 | _fs.Write(chunk, offset, len);
989 | offset += len;
990 | }
991 | }
992 | }
993 |
994 | public void Finish(FractionUInt32 averageFrameRate) {
995 | _fs.Close();
996 | }
997 |
998 | public string Path {
999 | get {
1000 | return _path;
1001 | }
1002 | }
1003 | }
1004 |
1005 | class WAVWriter : IAudioWriter {
1006 | string _path;
1007 | WAVTools.WAVWriter _wr;
1008 | int blockAlign;
1009 |
1010 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) {
1011 | _path = path;
1012 | _wr = new WAVTools.WAVWriter(path, bitsPerSample, channelCount, sampleRate);
1013 | blockAlign = (bitsPerSample / 8) * channelCount;
1014 | }
1015 |
1016 | public void WriteChunk(byte[] chunk, uint timeStamp) {
1017 | _wr.Write(chunk, chunk.Length / blockAlign);
1018 | }
1019 |
1020 | public void Finish() {
1021 | _wr.Close();
1022 | }
1023 |
1024 | public string Path {
1025 | get {
1026 | return _path;
1027 | }
1028 | }
1029 | }
1030 |
1031 | class AVIWriter : IVideoWriter {
1032 | string _path;
1033 | BinaryWriter _bw;
1034 | int _codecID;
1035 | int _width, _height, _frameCount;
1036 | uint _moviDataSize, _indexChunkSize;
1037 | List _index;
1038 | bool _isAlphaWriter;
1039 | AVIWriter _alphaWriter;
1040 | List _warnings;
1041 |
1042 | // Chunk: Off: Len:
1043 | //
1044 | // RIFF AVI 0 12
1045 | // LIST hdrl 12 12
1046 | // avih 24 64
1047 | // LIST strl 88 12
1048 | // strh 100 64
1049 | // strf 164 48
1050 | // LIST movi 212 12
1051 | // (frames) 224 ???
1052 | // idx1 ??? ???
1053 |
1054 | public AVIWriter(string path, int codecID, List warnings) :
1055 | this(path, codecID, warnings, false) { }
1056 |
1057 | private AVIWriter(string path, int codecID, List warnings, bool isAlphaWriter) {
1058 | if ((codecID != 2) && (codecID != 4) && (codecID != 5)) {
1059 | throw new Exception("Unsupported video codec.");
1060 | }
1061 |
1062 | FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
1063 |
1064 | _path = path;
1065 | _bw = new BinaryWriter(fs);
1066 | _codecID = codecID;
1067 | _warnings = warnings;
1068 | _isAlphaWriter = isAlphaWriter;
1069 |
1070 | if ((codecID == 5) && !_isAlphaWriter) {
1071 | _alphaWriter = new AVIWriter(path.Substring(0, path.Length - 4) + ".alpha.avi", codecID, warnings, true);
1072 | }
1073 |
1074 | WriteFourCC("RIFF");
1075 | _bw.Write((uint)0); // chunk size
1076 | WriteFourCC("AVI ");
1077 |
1078 | WriteFourCC("LIST");
1079 | _bw.Write((uint)192);
1080 | WriteFourCC("hdrl");
1081 |
1082 | WriteFourCC("avih");
1083 | _bw.Write((uint)56);
1084 | _bw.Write((uint)0);
1085 | _bw.Write((uint)0);
1086 | _bw.Write((uint)0);
1087 | _bw.Write((uint)0x10);
1088 | _bw.Write((uint)0); // frame count
1089 | _bw.Write((uint)0);
1090 | _bw.Write((uint)1);
1091 | _bw.Write((uint)0);
1092 | _bw.Write((uint)0); // width
1093 | _bw.Write((uint)0); // height
1094 | _bw.Write((uint)0);
1095 | _bw.Write((uint)0);
1096 | _bw.Write((uint)0);
1097 | _bw.Write((uint)0);
1098 |
1099 | WriteFourCC("LIST");
1100 | _bw.Write((uint)116);
1101 | WriteFourCC("strl");
1102 |
1103 | WriteFourCC("strh");
1104 | _bw.Write((uint)56);
1105 | WriteFourCC("vids");
1106 | WriteFourCC(CodecFourCC);
1107 | _bw.Write((uint)0);
1108 | _bw.Write((uint)0);
1109 | _bw.Write((uint)0);
1110 | _bw.Write((uint)0); // frame rate denominator
1111 | _bw.Write((uint)0); // frame rate numerator
1112 | _bw.Write((uint)0);
1113 | _bw.Write((uint)0); // frame count
1114 | _bw.Write((uint)0);
1115 | _bw.Write((int)-1);
1116 | _bw.Write((uint)0);
1117 | _bw.Write((ushort)0);
1118 | _bw.Write((ushort)0);
1119 | _bw.Write((ushort)0); // width
1120 | _bw.Write((ushort)0); // height
1121 |
1122 | WriteFourCC("strf");
1123 | _bw.Write((uint)40);
1124 | _bw.Write((uint)40);
1125 | _bw.Write((uint)0); // width
1126 | _bw.Write((uint)0); // height
1127 | _bw.Write((ushort)1);
1128 | _bw.Write((ushort)24);
1129 | WriteFourCC(CodecFourCC);
1130 | _bw.Write((uint)0); // biSizeImage
1131 | _bw.Write((uint)0);
1132 | _bw.Write((uint)0);
1133 | _bw.Write((uint)0);
1134 | _bw.Write((uint)0);
1135 |
1136 | WriteFourCC("LIST");
1137 | _bw.Write((uint)0); // chunk size
1138 | WriteFourCC("movi");
1139 |
1140 | _index = new List();
1141 | }
1142 |
1143 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) {
1144 | int offset, len;
1145 |
1146 | offset = 0;
1147 | len = chunk.Length;
1148 | if (_codecID == 4) {
1149 | offset = 1;
1150 | len -= 1;
1151 | }
1152 | if (_codecID == 5) {
1153 | offset = 4;
1154 | if (len >= 4) {
1155 | int alphaOffset = (int)BitConverterBE.ToUInt32(chunk, 0) & 0xFFFFFF;
1156 | if (!_isAlphaWriter) {
1157 | len = alphaOffset;
1158 | }
1159 | else {
1160 | offset += alphaOffset;
1161 | len -= offset;
1162 | }
1163 | }
1164 | else {
1165 | len = 0;
1166 | }
1167 | }
1168 | len = Math.Max(len, 0);
1169 | len = Math.Min(len, chunk.Length - offset);
1170 |
1171 | _index.Add((frameType == 1) ? (uint)0x10 : (uint)0);
1172 | _index.Add(_moviDataSize + 4);
1173 | _index.Add((uint)len);
1174 |
1175 | if ((_width == 0) && (_height == 0)) {
1176 | GetFrameSize(chunk);
1177 | }
1178 |
1179 | WriteFourCC("00dc");
1180 | _bw.Write(len);
1181 | _bw.Write(chunk, offset, len);
1182 |
1183 | if ((len % 2) != 0) {
1184 | _bw.Write((byte)0);
1185 | len++;
1186 | }
1187 | _moviDataSize += (uint)len + 8;
1188 | _frameCount++;
1189 |
1190 | if (_alphaWriter != null) {
1191 | _alphaWriter.WriteChunk(chunk, timeStamp, frameType);
1192 | }
1193 | }
1194 |
1195 | private void GetFrameSize(byte[] chunk) {
1196 | if (_codecID == 2) {
1197 | // Reference: flv_h263_decode_picture_header from libavcodec's h263.c
1198 |
1199 | if (chunk.Length < 10) return;
1200 |
1201 | if ((chunk[0] != 0) || (chunk[1] != 0)) {
1202 | return;
1203 | }
1204 |
1205 | ulong x = BitConverterBE.ToUInt64(chunk, 2);
1206 | int format;
1207 |
1208 | if (BitHelper.Read(ref x, 1) != 1) {
1209 | return;
1210 | }
1211 | BitHelper.Read(ref x, 5);
1212 | BitHelper.Read(ref x, 8);
1213 |
1214 | format = BitHelper.Read(ref x, 3);
1215 | switch (format) {
1216 | case 0:
1217 | _width = BitHelper.Read(ref x, 8);
1218 | _height = BitHelper.Read(ref x, 8);
1219 | break;
1220 | case 1:
1221 | _width = BitHelper.Read(ref x, 16);
1222 | _height = BitHelper.Read(ref x, 16);
1223 | break;
1224 | case 2:
1225 | _width = 352;
1226 | _height = 288;
1227 | break;
1228 | case 3:
1229 | _width = 176;
1230 | _height = 144;
1231 | break;
1232 | case 4:
1233 | _width = 128;
1234 | _height = 96;
1235 | break;
1236 | case 5:
1237 | _width = 320;
1238 | _height = 240;
1239 | break;
1240 | case 6:
1241 | _width = 160;
1242 | _height = 120;
1243 | break;
1244 | default:
1245 | return;
1246 | }
1247 | }
1248 | else if ((_codecID == 4) || (_codecID == 5)) {
1249 | // Reference: vp6_parse_header from libavcodec's vp6.c
1250 |
1251 | int skip = (_codecID == 4) ? 1 : 4;
1252 | if (chunk.Length < (skip + 8)) return;
1253 | ulong x = BitConverterBE.ToUInt64(chunk, skip);
1254 |
1255 | int deltaFrameFlag = BitHelper.Read(ref x, 1);
1256 | int quant = BitHelper.Read(ref x, 6);
1257 | int separatedCoeffFlag = BitHelper.Read(ref x, 1);
1258 | int subVersion = BitHelper.Read(ref x, 5);
1259 | int filterHeader = BitHelper.Read(ref x, 2);
1260 | int interlacedFlag = BitHelper.Read(ref x, 1);
1261 |
1262 | if (deltaFrameFlag != 0) {
1263 | return;
1264 | }
1265 | if ((separatedCoeffFlag != 0) || (filterHeader == 0)) {
1266 | BitHelper.Read(ref x, 16);
1267 | }
1268 |
1269 | _height = BitHelper.Read(ref x, 8) * 16;
1270 | _width = BitHelper.Read(ref x, 8) * 16;
1271 |
1272 | // chunk[0] contains the width and height (4 bits each, respectively) that should
1273 | // be cropped off during playback, which will be non-zero if the encoder padded
1274 | // the frames to a macroblock boundary. But if you use this adjusted size in the
1275 | // AVI header, DirectShow seems to ignore it, and it can cause stride or chroma
1276 | // alignment problems with VFW if the width/height aren't multiples of 4.
1277 | if (!_isAlphaWriter) {
1278 | int cropX = chunk[0] >> 4;
1279 | int cropY = chunk[0] & 0x0F;
1280 | if (((cropX != 0) || (cropY != 0)) && !_isAlphaWriter) {
1281 | _warnings.Add(String.Format("Suggested cropping: {0} pixels from right, {1} pixels from bottom.", cropX, cropY));
1282 | }
1283 | }
1284 | }
1285 | }
1286 |
1287 | private string CodecFourCC {
1288 | get {
1289 | if (_codecID == 2) {
1290 | return "FLV1";
1291 | }
1292 | if ((_codecID == 4) || (_codecID == 5)) {
1293 | return "VP6F";
1294 | }
1295 | return "NULL";
1296 | }
1297 | }
1298 |
1299 | private void WriteIndexChunk() {
1300 | uint indexDataSize = (uint)_frameCount * 16;
1301 |
1302 | WriteFourCC("idx1");
1303 | _bw.Write(indexDataSize);
1304 |
1305 | for (int i = 0; i < _frameCount; i++) {
1306 | WriteFourCC("00dc");
1307 | _bw.Write(_index[(i * 3) + 0]);
1308 | _bw.Write(_index[(i * 3) + 1]);
1309 | _bw.Write(_index[(i * 3) + 2]);
1310 | }
1311 |
1312 | _indexChunkSize = indexDataSize + 8;
1313 | }
1314 |
1315 | public void Finish(FractionUInt32 averageFrameRate) {
1316 | WriteIndexChunk();
1317 |
1318 | _bw.BaseStream.Seek(4, SeekOrigin.Begin);
1319 | _bw.Write((uint)(224 + _moviDataSize + _indexChunkSize - 8));
1320 |
1321 | _bw.BaseStream.Seek(24 + 8, SeekOrigin.Begin);
1322 | _bw.Write((uint)0);
1323 | _bw.BaseStream.Seek(12, SeekOrigin.Current);
1324 | _bw.Write((uint)_frameCount);
1325 | _bw.BaseStream.Seek(12, SeekOrigin.Current);
1326 | _bw.Write((uint)_width);
1327 | _bw.Write((uint)_height);
1328 |
1329 | _bw.BaseStream.Seek(100 + 28, SeekOrigin.Begin);
1330 | _bw.Write((uint)averageFrameRate.D);
1331 | _bw.Write((uint)averageFrameRate.N);
1332 | _bw.BaseStream.Seek(4, SeekOrigin.Current);
1333 | _bw.Write((uint)_frameCount);
1334 | _bw.BaseStream.Seek(16, SeekOrigin.Current);
1335 | _bw.Write((ushort)_width);
1336 | _bw.Write((ushort)_height);
1337 |
1338 | _bw.BaseStream.Seek(164 + 12, SeekOrigin.Begin);
1339 | _bw.Write((uint)_width);
1340 | _bw.Write((uint)_height);
1341 | _bw.BaseStream.Seek(8, SeekOrigin.Current);
1342 | _bw.Write((uint)(_width * _height * 6));
1343 |
1344 | _bw.BaseStream.Seek(212 + 4, SeekOrigin.Begin);
1345 | _bw.Write((uint)(_moviDataSize + 4));
1346 |
1347 | _bw.Close();
1348 |
1349 | if (_alphaWriter != null) {
1350 | _alphaWriter.Finish(averageFrameRate);
1351 | }
1352 | }
1353 |
1354 | private void WriteFourCC(string fourCC) {
1355 | byte[] bytes = System.Text.Encoding.ASCII.GetBytes(fourCC);
1356 | if (bytes.Length != 4) {
1357 | throw new Exception("Invalid FourCC length.");
1358 | }
1359 | _bw.Write(bytes);
1360 | }
1361 |
1362 | public string Path {
1363 | get {
1364 | return _path;
1365 | }
1366 | }
1367 | }
1368 |
1369 | class TimeCodeWriter {
1370 | string _path;
1371 | StreamWriter _sw;
1372 |
1373 | public TimeCodeWriter(string path) {
1374 | _path = path;
1375 | if (path != null) {
1376 | _sw = new StreamWriter(path, false, Encoding.ASCII);
1377 | _sw.WriteLine("# timecode format v2");
1378 | }
1379 | }
1380 |
1381 | public void Write(uint timeStamp) {
1382 | if (_sw != null) {
1383 | _sw.WriteLine(timeStamp);
1384 | }
1385 | }
1386 |
1387 | public void Finish() {
1388 | if (_sw != null) {
1389 | _sw.Close();
1390 | _sw = null;
1391 | }
1392 | }
1393 |
1394 | public string Path {
1395 | get {
1396 | return _path;
1397 | }
1398 | }
1399 | }
1400 |
1401 | public struct FractionUInt32 {
1402 | public uint N;
1403 | public uint D;
1404 |
1405 | public FractionUInt32(uint n, uint d) {
1406 | N = n;
1407 | D = d;
1408 | }
1409 |
1410 | public double ToDouble() {
1411 | return (double)N / (double)D;
1412 | }
1413 |
1414 | public void Reduce() {
1415 | uint gcd = GCD(N, D);
1416 | N /= gcd;
1417 | D /= gcd;
1418 | }
1419 |
1420 | private uint GCD(uint a, uint b) {
1421 | uint r;
1422 |
1423 | while (b != 0) {
1424 | r = a % b;
1425 | a = b;
1426 | b = r;
1427 | }
1428 |
1429 | return a;
1430 | }
1431 |
1432 | public override string ToString() {
1433 | return ToString(true);
1434 | }
1435 |
1436 | public string ToString(bool full) {
1437 | if (full) {
1438 | return ToDouble().ToString() + " (" + N.ToString() + "/" + D.ToString() + ")";
1439 | }
1440 | else {
1441 | return ToDouble().ToString("0.####");
1442 | }
1443 | }
1444 | }
1445 | }
1446 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/General.cs:
--------------------------------------------------------------------------------
1 | // ****************************************************************************
2 | //
3 | // FLV Extract
4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com)
5 | //
6 | // This program is free software; you can redistribute it and/or modify
7 | // it under the terms of the GNU General Public License as published by
8 | // the Free Software Foundation; either version 2 of the License, or
9 | // (at your option) any later version.
10 | //
11 | // This program is distributed in the hope that it will be useful,
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | // GNU General Public License for more details.
15 | //
16 | // You should have received a copy of the GNU General Public License
17 | // along with this program; if not, write to the Free Software
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | //
20 | // ****************************************************************************
21 |
22 | using System;
23 | using System.Reflection;
24 |
25 | namespace QiyiFLV2MP4_GUI
26 | {
27 | public static class General {
28 | public static string Version {
29 | get {
30 | Version ver = Assembly.GetExecutingAssembly().GetName().Version;
31 | return ver.Major + "." + ver.Minor + "." + ver.Revision;
32 | }
33 | }
34 |
35 | public static void CopyBytes(byte[] dst, int dstOffset, byte[] src) {
36 | Buffer.BlockCopy(src, 0, dst, dstOffset, src.Length);
37 | }
38 | }
39 |
40 | public static class BitHelper {
41 | public static int Read(ref ulong x, int length) {
42 | int r = (int)(x >> (64 - length));
43 | x <<= length;
44 | return r;
45 | }
46 |
47 | public static int Read(byte[] bytes, ref int offset, int length) {
48 | int startByte = offset / 8;
49 | int endByte = (offset + length - 1) / 8;
50 | int skipBits = offset % 8;
51 | ulong bits = 0;
52 | for (int i = 0; i <= Math.Min(endByte - startByte, 7); i++) {
53 | bits |= (ulong)bytes[startByte + i] << (56 - (i * 8));
54 | }
55 | if (skipBits != 0) Read(ref bits, skipBits);
56 | offset += length;
57 | return Read(ref bits, length);
58 | }
59 |
60 | public static void Write(ref ulong x, int length, int value) {
61 | ulong mask = 0xFFFFFFFFFFFFFFFF >> (64 - length);
62 | x = (x << length) | ((ulong)value & mask);
63 | }
64 |
65 | public static byte[] CopyBlock(byte[] bytes, int offset, int length) {
66 | int startByte = offset / 8;
67 | int endByte = (offset + length - 1) / 8;
68 | int shiftA = offset % 8;
69 | int shiftB = 8 - shiftA;
70 | byte[] dst = new byte[(length + 7) / 8];
71 | if (shiftA == 0) {
72 | Buffer.BlockCopy(bytes, startByte, dst, 0, dst.Length);
73 | }
74 | else {
75 | int i;
76 | for (i = 0; i < endByte - startByte; i++) {
77 | dst[i] = (byte)((bytes[startByte + i] << shiftA) | (bytes[startByte + i + 1] >> shiftB));
78 | }
79 | if (i < dst.Length) {
80 | dst[i] = (byte)(bytes[startByte + i] << shiftA);
81 | }
82 | }
83 | dst[dst.Length - 1] &= (byte)(0xFF << ((dst.Length * 8) - length));
84 | return dst;
85 | }
86 | }
87 |
88 | public static class BitConverterBE {
89 | public static ulong ToUInt64(byte[] value, int startIndex) {
90 | return
91 | ((ulong)value[startIndex ] << 56) |
92 | ((ulong)value[startIndex + 1] << 48) |
93 | ((ulong)value[startIndex + 2] << 40) |
94 | ((ulong)value[startIndex + 3] << 32) |
95 | ((ulong)value[startIndex + 4] << 24) |
96 | ((ulong)value[startIndex + 5] << 16) |
97 | ((ulong)value[startIndex + 6] << 8) |
98 | ((ulong)value[startIndex + 7] );
99 | }
100 |
101 | public static uint ToUInt32(byte[] value, int startIndex) {
102 | return
103 | ((uint)value[startIndex ] << 24) |
104 | ((uint)value[startIndex + 1] << 16) |
105 | ((uint)value[startIndex + 2] << 8) |
106 | ((uint)value[startIndex + 3] );
107 | }
108 |
109 | public static ushort ToUInt16(byte[] value, int startIndex) {
110 | return (ushort)(
111 | (value[startIndex ] << 8) |
112 | (value[startIndex + 1] ));
113 | }
114 |
115 | public static byte[] GetBytes(ulong value) {
116 | byte[] buff = new byte[8];
117 | buff[0] = (byte)(value >> 56);
118 | buff[1] = (byte)(value >> 48);
119 | buff[2] = (byte)(value >> 40);
120 | buff[3] = (byte)(value >> 32);
121 | buff[4] = (byte)(value >> 24);
122 | buff[5] = (byte)(value >> 16);
123 | buff[6] = (byte)(value >> 8);
124 | buff[7] = (byte)(value );
125 | return buff;
126 | }
127 |
128 | public static byte[] GetBytes(uint value) {
129 | byte[] buff = new byte[4];
130 | buff[0] = (byte)(value >> 24);
131 | buff[1] = (byte)(value >> 16);
132 | buff[2] = (byte)(value >> 8);
133 | buff[3] = (byte)(value );
134 | return buff;
135 | }
136 |
137 | public static byte[] GetBytes(ushort value) {
138 | byte[] buff = new byte[2];
139 | buff[0] = (byte)(value >> 8);
140 | buff[1] = (byte)(value );
141 | return buff;
142 | }
143 | }
144 |
145 | public static class BitConverterLE {
146 | public static byte[] GetBytes(ulong value) {
147 | byte[] buff = new byte[8];
148 | buff[0] = (byte)(value );
149 | buff[1] = (byte)(value >> 8);
150 | buff[2] = (byte)(value >> 16);
151 | buff[3] = (byte)(value >> 24);
152 | buff[4] = (byte)(value >> 32);
153 | buff[5] = (byte)(value >> 40);
154 | buff[6] = (byte)(value >> 48);
155 | buff[7] = (byte)(value >> 56);
156 | return buff;
157 | }
158 |
159 | public static byte[] GetBytes(uint value) {
160 | byte[] buff = new byte[4];
161 | buff[0] = (byte)(value );
162 | buff[1] = (byte)(value >> 8);
163 | buff[2] = (byte)(value >> 16);
164 | buff[3] = (byte)(value >> 24);
165 | return buff;
166 | }
167 |
168 | public static byte[] GetBytes(ushort value) {
169 | byte[] buff = new byte[2];
170 | buff[0] = (byte)(value );
171 | buff[1] = (byte)(value >> 8);
172 | return buff;
173 | }
174 | }
175 |
176 | public static class OggCRC {
177 | static uint[] _lut = new uint[256];
178 |
179 | static OggCRC() {
180 | for (uint i = 0; i < 256; i++) {
181 | uint x = i << 24;
182 | for (uint j = 0; j < 8; j++) {
183 | x = ((x & 0x80000000U) != 0) ? ((x << 1) ^ 0x04C11DB7) : (x << 1);
184 | }
185 | _lut[i] = x;
186 | }
187 | }
188 |
189 | public static uint Calculate(byte[] buff, int offset, int length) {
190 | uint crc = 0;
191 | for (int i = 0; i < length; i++) {
192 | crc = _lut[((crc >> 24) ^ buff[offset + i]) & 0xFF] ^ (crc << 8);
193 | }
194 | return crc;
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/MP4Box.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/MP4Box.exe
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/WAVFile.cs:
--------------------------------------------------------------------------------
1 | // ****************************************************************************
2 | //
3 | // FLV Extract
4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com)
5 | //
6 | // This program is free software; you can redistribute it and/or modify
7 | // it under the terms of the GNU General Public License as published by
8 | // the Free Software Foundation; either version 2 of the License, or
9 | // (at your option) any later version.
10 | //
11 | // This program is distributed in the hope that it will be useful,
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | // GNU General Public License for more details.
15 | //
16 | // You should have received a copy of the GNU General Public License
17 | // along with this program; if not, write to the Free Software
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | //
20 | // ****************************************************************************
21 |
22 | using System;
23 | using System.IO;
24 |
25 | namespace WAVTools {
26 | public class WAVWriter {
27 | BinaryWriter _bw;
28 | bool _canSeek;
29 | bool _wroteHeaders;
30 | int _bitsPerSample, _channelCount, _sampleRate, _blockAlign;
31 | long _finalSampleLen, _sampleLen;
32 |
33 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) :
34 | this(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read),
35 | bitsPerSample, channelCount, sampleRate)
36 | {
37 | }
38 |
39 | public WAVWriter(Stream stream, int bitsPerSample, int channelCount, int sampleRate) {
40 | _bitsPerSample = bitsPerSample;
41 | _channelCount = channelCount;
42 | _sampleRate = sampleRate;
43 | _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8);
44 |
45 | _bw = new BinaryWriter(stream);
46 | _canSeek = stream.CanSeek;
47 | }
48 |
49 | private void WriteHeaders() {
50 | const uint fccRIFF = 0x46464952;
51 | const uint fccWAVE = 0x45564157;
52 | const uint fccFormat = 0x20746D66;
53 | const uint fccData = 0x61746164;
54 |
55 | uint dataChunkSize = GetDataChunkSize(_finalSampleLen);
56 |
57 | _bw.Write(fccRIFF);
58 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36));
59 | _bw.Write(fccWAVE);
60 |
61 | _bw.Write(fccFormat);
62 | _bw.Write((uint)16);
63 | _bw.Write((ushort)1);
64 | _bw.Write((ushort)_channelCount);
65 | _bw.Write((uint)_sampleRate);
66 | _bw.Write((uint)(_sampleRate * _blockAlign));
67 | _bw.Write((ushort)_blockAlign);
68 | _bw.Write((ushort)_bitsPerSample);
69 |
70 | _bw.Write(fccData);
71 | _bw.Write((uint)dataChunkSize);
72 | }
73 |
74 | private uint GetDataChunkSize(long sampleCount) {
75 | const long maxFileSize = 0x7FFFFFFEL;
76 | long dataSize = sampleCount * _blockAlign;
77 | if ((dataSize + 44) > maxFileSize) {
78 | dataSize = ((maxFileSize - 44) / _blockAlign) * _blockAlign;
79 | }
80 | return (uint)dataSize;
81 | }
82 |
83 | public void Close() {
84 | if (((_sampleLen * _blockAlign) & 1) == 1) {
85 | _bw.Write((byte)0);
86 | }
87 |
88 | try {
89 | if (_sampleLen != _finalSampleLen) {
90 | if (_canSeek) {
91 | uint dataChunkSize = GetDataChunkSize(_sampleLen);
92 | _bw.Seek(4, SeekOrigin.Begin);
93 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36));
94 | _bw.Seek(40, SeekOrigin.Begin);
95 | _bw.Write((uint)dataChunkSize);
96 | }
97 | else {
98 | throw new Exception("Samples written differs from the expected sample count.");
99 | }
100 | }
101 | }
102 | finally {
103 | _bw.Close();
104 | _bw = null;
105 | }
106 | }
107 |
108 | public long Position {
109 | get {
110 | return _sampleLen;
111 | }
112 | }
113 |
114 | public long FinalSampleCount {
115 | set {
116 | _finalSampleLen = value;
117 | }
118 | }
119 |
120 | public void Write(byte[] buff, int sampleCount) {
121 | if (sampleCount <= 0) return;
122 |
123 | if (!_wroteHeaders) {
124 | WriteHeaders();
125 | _wroteHeaders = true;
126 | }
127 |
128 | _bw.Write(buff, 0, sampleCount * _blockAlign);
129 | _sampleLen += sampleCount;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/js.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/js.dll
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/js32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/js32.dll
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Library/libgpac.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4 GUI/Library/libgpac.dll
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Main.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace QiyiFLV2MP4_GUI
2 | {
3 | partial class Main
4 | {
5 | ///
6 | /// 必需的设计器变量。
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// 清理所有正在使用的资源。
12 | ///
13 | /// 如果应释放托管资源,为 true;否则为 false。
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows 窗体设计器生成的代码
24 |
25 | ///
26 | /// 设计器支持所需的方法 - 不要修改
27 | /// 使用代码编辑器修改此方法的内容。
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.groupBox1 = new System.Windows.Forms.GroupBox();
32 | this.groupBox2 = new System.Windows.Forms.GroupBox();
33 | this.listBox1 = new System.Windows.Forms.ListBox();
34 | this.button1 = new System.Windows.Forms.Button();
35 | this.button2 = new System.Windows.Forms.Button();
36 | this.button3 = new System.Windows.Forms.Button();
37 | this.button4 = new System.Windows.Forms.Button();
38 | this.label1 = new System.Windows.Forms.Label();
39 | this.label2 = new System.Windows.Forms.Label();
40 | this.label3 = new System.Windows.Forms.Label();
41 | this.richTextBox1 = new System.Windows.Forms.RichTextBox();
42 | this.groupBox1.SuspendLayout();
43 | this.groupBox2.SuspendLayout();
44 | this.SuspendLayout();
45 | //
46 | // groupBox1
47 | //
48 | this.groupBox1.Controls.Add(this.button4);
49 | this.groupBox1.Controls.Add(this.button3);
50 | this.groupBox1.Controls.Add(this.button2);
51 | this.groupBox1.Controls.Add(this.button1);
52 | this.groupBox1.Controls.Add(this.listBox1);
53 | this.groupBox1.Location = new System.Drawing.Point(12, 12);
54 | this.groupBox1.Name = "groupBox1";
55 | this.groupBox1.Size = new System.Drawing.Size(448, 144);
56 | this.groupBox1.TabIndex = 0;
57 | this.groupBox1.TabStop = false;
58 | this.groupBox1.Text = "操作";
59 | //
60 | // groupBox2
61 | //
62 | this.groupBox2.Controls.Add(this.richTextBox1);
63 | this.groupBox2.Controls.Add(this.label3);
64 | this.groupBox2.Controls.Add(this.label2);
65 | this.groupBox2.Controls.Add(this.label1);
66 | this.groupBox2.Location = new System.Drawing.Point(12, 162);
67 | this.groupBox2.Name = "groupBox2";
68 | this.groupBox2.Size = new System.Drawing.Size(448, 121);
69 | this.groupBox2.TabIndex = 1;
70 | this.groupBox2.TabStop = false;
71 | this.groupBox2.Text = "状态";
72 | //
73 | // listBox1
74 | //
75 | this.listBox1.AllowDrop = true;
76 | this.listBox1.FormattingEnabled = true;
77 | this.listBox1.ItemHeight = 12;
78 | this.listBox1.Location = new System.Drawing.Point(6, 20);
79 | this.listBox1.Name = "listBox1";
80 | this.listBox1.Size = new System.Drawing.Size(351, 112);
81 | this.listBox1.TabIndex = 0;
82 | this.listBox1.DragDrop += new System.Windows.Forms.DragEventHandler(this.listBox1_DragDrop);
83 | this.listBox1.DragEnter += new System.Windows.Forms.DragEventHandler(this.listBox1_DragEnter);
84 | this.listBox1.DragOver += new System.Windows.Forms.DragEventHandler(this.listBox1_DragOver);
85 | //
86 | // button1
87 | //
88 | this.button1.Location = new System.Drawing.Point(364, 21);
89 | this.button1.Name = "button1";
90 | this.button1.Size = new System.Drawing.Size(75, 23);
91 | this.button1.TabIndex = 1;
92 | this.button1.Text = "添加文件";
93 | this.button1.UseVisualStyleBackColor = true;
94 | this.button1.Click += new System.EventHandler(this.button1_Click);
95 | //
96 | // button2
97 | //
98 | this.button2.Location = new System.Drawing.Point(364, 50);
99 | this.button2.Name = "button2";
100 | this.button2.Size = new System.Drawing.Size(75, 23);
101 | this.button2.TabIndex = 2;
102 | this.button2.Text = "删除文件";
103 | this.button2.UseVisualStyleBackColor = true;
104 | this.button2.Click += new System.EventHandler(this.button2_Click);
105 | //
106 | // button3
107 | //
108 | this.button3.Location = new System.Drawing.Point(364, 79);
109 | this.button3.Name = "button3";
110 | this.button3.Size = new System.Drawing.Size(75, 23);
111 | this.button3.TabIndex = 3;
112 | this.button3.Text = "清空列表";
113 | this.button3.UseVisualStyleBackColor = true;
114 | this.button3.Click += new System.EventHandler(this.button3_Click);
115 | //
116 | // button4
117 | //
118 | this.button4.Enabled = false;
119 | this.button4.Location = new System.Drawing.Point(364, 108);
120 | this.button4.Name = "button4";
121 | this.button4.Size = new System.Drawing.Size(75, 23);
122 | this.button4.TabIndex = 4;
123 | this.button4.Text = "开始封装";
124 | this.button4.UseVisualStyleBackColor = true;
125 | this.button4.Click += new System.EventHandler(this.button4_Click);
126 | //
127 | // label1
128 | //
129 | this.label1.AutoSize = true;
130 | this.label1.Location = new System.Drawing.Point(6, 17);
131 | this.label1.Name = "label1";
132 | this.label1.Size = new System.Drawing.Size(89, 12);
133 | this.label1.TabIndex = 0;
134 | this.label1.Text = "当前封装文件:";
135 | //
136 | // label2
137 | //
138 | this.label2.AutoSize = true;
139 | this.label2.Location = new System.Drawing.Point(6, 31);
140 | this.label2.Name = "label2";
141 | this.label2.Size = new System.Drawing.Size(65, 12);
142 | this.label2.TabIndex = 1;
143 | this.label2.Text = "调试输出:";
144 | //
145 | // label3
146 | //
147 | this.label3.AutoSize = true;
148 | this.label3.Location = new System.Drawing.Point(90, 17);
149 | this.label3.Name = "label3";
150 | this.label3.Size = new System.Drawing.Size(101, 12);
151 | this.label3.TabIndex = 2;
152 | this.label3.Text = "尚未执行封装操作";
153 | //
154 | // richTextBox1
155 | //
156 | this.richTextBox1.Location = new System.Drawing.Point(8, 45);
157 | this.richTextBox1.Name = "richTextBox1";
158 | this.richTextBox1.ReadOnly = true;
159 | this.richTextBox1.ScrollBars = System.Windows.Forms.RichTextBoxScrollBars.ForcedVertical;
160 | this.richTextBox1.Size = new System.Drawing.Size(431, 70);
161 | this.richTextBox1.TabIndex = 3;
162 | this.richTextBox1.Text = "";
163 | this.richTextBox1.TextChanged += new System.EventHandler(this.richTextBox1_TextChanged);
164 | //
165 | // Main
166 | //
167 | this.AllowDrop = true;
168 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
169 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
170 | this.ClientSize = new System.Drawing.Size(472, 290);
171 | this.Controls.Add(this.groupBox2);
172 | this.Controls.Add(this.groupBox1);
173 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
174 | this.MaximizeBox = false;
175 | this.Name = "Main";
176 | this.ShowIcon = false;
177 | this.Text = "爱奇艺FLV重封装工具";
178 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.Main_FormClosed);
179 | this.Load += new System.EventHandler(this.Main_Load);
180 | this.groupBox1.ResumeLayout(false);
181 | this.groupBox2.ResumeLayout(false);
182 | this.groupBox2.PerformLayout();
183 | this.ResumeLayout(false);
184 |
185 | }
186 |
187 | #endregion
188 |
189 | private System.Windows.Forms.GroupBox groupBox1;
190 | private System.Windows.Forms.GroupBox groupBox2;
191 | private System.Windows.Forms.Button button4;
192 | private System.Windows.Forms.Button button3;
193 | private System.Windows.Forms.Button button2;
194 | private System.Windows.Forms.Button button1;
195 | private System.Windows.Forms.ListBox listBox1;
196 | private System.Windows.Forms.RichTextBox richTextBox1;
197 | private System.Windows.Forms.Label label3;
198 | private System.Windows.Forms.Label label2;
199 | private System.Windows.Forms.Label label1;
200 | }
201 | }
202 |
203 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Main.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Data;
5 | using System.Diagnostics;
6 | using System.Drawing;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Windows.Forms;
12 |
13 | namespace QiyiFLV2MP4_GUI
14 | {
15 | public partial class Main : Form
16 | {
17 | public Main()
18 | {
19 | InitializeComponent();
20 | }
21 |
22 | public int ConvertSuccess = 0;
23 |
24 | private void button1_Click(object sender, EventArgs e)
25 | {
26 | OpenFileDialog dlg = new OpenFileDialog();
27 | dlg.Multiselect = true;
28 | dlg.Filter = "FLV文件|*.FLV";
29 | if (dlg.ShowDialog() == DialogResult.OK)
30 | {
31 | foreach (string s in dlg.FileNames)
32 | {
33 | if (s.Contains("•"))
34 | {
35 | MessageBox.Show("文件路径中发现非法字符\"•\",请重命名 " + s + " 后重试!", "温馨提示");
36 | listBox1.Items.Clear();
37 | break;
38 | }
39 | listBox1.Items.Add(s);
40 | }
41 | }
42 | if (listBox1.Items.Count == 0)
43 | {
44 | button4.Enabled = false;
45 | }
46 | else
47 | {
48 | button4.Enabled = true;
49 | }
50 | }
51 |
52 | private void button2_Click(object sender, EventArgs e)
53 | {
54 | try
55 | {
56 | listBox1.Items.RemoveAt(listBox1.SelectedIndex);
57 | }
58 | catch
59 | {
60 | MessageBox.Show("请选择你要从列表中删除的文件!", "温馨提示");
61 | }
62 | if (listBox1.Items.Count == 0)
63 | {
64 | button4.Enabled = false;
65 | }
66 | else
67 | {
68 | button4.Enabled = true;
69 | }
70 | }
71 |
72 | private void button3_Click(object sender, EventArgs e)
73 | {
74 | listBox1.Items.Clear();
75 | button4.Enabled = false;
76 | }
77 |
78 | private void button4_Click(object sender, EventArgs e)
79 | {
80 |
81 | DialogResult dr = MessageBox.Show("封装完成后是否删除FLV文件?", "提示", MessageBoxButtons.OKCancel);
82 | if (dr == DialogResult.OK)
83 | {
84 | //用户选择确认的操作
85 | foreach (string s in listBox1.Items)
86 | {
87 | label3.Text = s;
88 | if (!convert(s,true))
89 | {
90 | MessageBox.Show("转换失败,请尝试重试!", "提示");
91 | }
92 | }
93 | }
94 | else if (dr == DialogResult.Cancel)
95 | {
96 | //用户选择取消的操作
97 | foreach (string s in listBox1.Items)
98 | {
99 | label3.Text = s;
100 | if (!convert(s,false))
101 | {
102 | MessageBox.Show("转换失败,请尝试重试!", "提示");
103 | }
104 | }
105 | }
106 | button4.Enabled = false;
107 | MessageBox.Show(ConvertSuccess.ToString() + "个FLV文件封装成功!" + (listBox1.Items.Count - ConvertSuccess).ToString() + "个FLV文件封装失败!列表已清空!");
108 | label3.Text = "尚未执行封装操作";
109 | listBox1.Items.Clear();
110 | }
111 |
112 | static bool _autoOverwrite;
113 | private static StringBuilder sortOutput = null;
114 | public static string inputPath = "";
115 |
116 | private bool convert(string args,bool delete)
117 | {
118 | if(File.Exists(args.Substring(0, args.Length - 4) + ".mp4"))
119 | {
120 | DialogResult dr = MessageBox.Show("发现重名文件 "+ args.Substring(0, args.Length - 4) + ".mp4 ,是否删除?", "提示", MessageBoxButtons.OKCancel);
121 | if (dr == DialogResult.OK)
122 | {
123 | File.Delete(args.Substring(0, args.Length - 4) + ".mp4");
124 | }
125 | else if (dr == DialogResult.Cancel)
126 | {
127 | return false;
128 | }
129 | }
130 | bool extractVideo = false;
131 | bool extractAudio = false;
132 | bool extractTimeCodes = false;
133 | string outputDirectory = null;
134 |
135 | try
136 | {
137 | _autoOverwrite = true;
138 | extractVideo = true;
139 | extractAudio = true;
140 | inputPath = args;
141 | }
142 | catch
143 | {
144 | Console.WriteLine("Arguments: source_path");
145 | Console.WriteLine();
146 | return false;
147 | }
148 |
149 | richTextBox1.Text += "正在抽取视音频数据!\r\n";
150 | try
151 | {
152 | using (FLVFile flvFile = new FLVFile(Path.GetFullPath(inputPath)))
153 | {
154 | if (outputDirectory != null)
155 | {
156 | flvFile.OutputDirectory = Path.GetFullPath(outputDirectory);
157 | }
158 | flvFile.ExtractStreams(extractAudio, extractVideo, extractTimeCodes, PromptOverwrite);
159 | if ((flvFile.TrueFrameRate != null) || (flvFile.AverageFrameRate != null))
160 | {
161 | if (flvFile.TrueFrameRate != null)
162 | {
163 | richTextBox1.Text += "True Frame Rate: " + flvFile.TrueFrameRate.ToString() + "\r\n";
164 | }
165 | if (flvFile.AverageFrameRate != null)
166 | {
167 | richTextBox1.Text += "Average Frame Rate: " + flvFile.AverageFrameRate.ToString() + "\r\n";
168 | }
169 | }
170 | if (flvFile.Warnings.Length != 0)
171 | {
172 | foreach (string warning in flvFile.Warnings)
173 | {
174 | richTextBox1.Text += "Warning: " + warning +"\r\n";
175 | }
176 | }
177 | }
178 | }
179 | catch (Exception ex)
180 | {
181 | richTextBox1.Text += "Error: " + ex.Message + "\r\n";
182 | return false;
183 | }
184 |
185 | if (!File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".aac"))
186 | {
187 | MessageBox.Show("数据文件异常,请检查FLV文件后尝试!", "温馨提示");
188 | return false;
189 | }
190 | if (!File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".264"))
191 | {
192 | MessageBox.Show("数据文件异常,请检查FLV文件后尝试!", "温馨提示");
193 | return false;
194 | }
195 |
196 | Process sortProcess;
197 | sortProcess = new Process();
198 | sortOutput = new StringBuilder("");
199 | sortProcess.StartInfo.FileName = "cmd.exe";
200 | sortProcess.StartInfo.UseShellExecute = false;// 必须禁用操作系统外壳程序
201 | sortProcess.StartInfo.RedirectStandardOutput = true;
202 | sortProcess.StartInfo.RedirectStandardError = true; //重定向错误输出
203 | sortProcess.StartInfo.CreateNoWindow = true; //设置不显示窗口
204 | sortProcess.StartInfo.RedirectStandardInput = true;
205 | sortProcess.StartInfo.Arguments = "/c mp4box.exe -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".264#trackID=1:par=1:1:name=\" -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".aac:name=\" -new \"" + inputPath.Substring(0, inputPath.Length - 4) + ".mp4\""; //设定程式执行参数
206 | sortProcess.Start();
207 | richTextBox1.Text += "封装为MP4中!请耐心等待!\r\n";
208 | sortProcess.BeginOutputReadLine();// 异步获取命令行内容
209 | sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler); // 为异步获取订阅事件
210 | sortProcess.WaitForExit();//等待程序执行完退出进程
211 | sortProcess.Close();
212 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".aac"))
213 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".aac");
214 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".264"))
215 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".264");
216 | if(!richTextBox1.Text.Contains("ISO File Writing"))
217 | {
218 | MessageBox.Show("文件路径中可能存在非法字符,请重命名 " + args + " 后重试!", "温馨提示");
219 | return false;
220 | }
221 | if (delete)
222 | File.Delete(inputPath);
223 | richTextBox1.Text = "";
224 | ConvertSuccess++;
225 | return true;
226 | }
227 |
228 | private void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
229 | {
230 | if (!String.IsNullOrEmpty(outLine.Data))
231 | {
232 | this.richTextBox1.Text += outLine.Data + Environment.NewLine;
233 | }
234 | }
235 |
236 | private static bool PromptOverwrite(string path)
237 | {
238 | if (_autoOverwrite) return true;
239 | bool? overwrite = null;
240 | Console.Write("Output file \"" + Path.GetFileName(path) + "\" already exists, overwrite? (y/n): ");
241 | while (overwrite == null)
242 | {
243 | char c = Console.ReadKey(true).KeyChar;
244 | if (c == 'y') overwrite = true;
245 | if (c == 'n') overwrite = false;
246 | }
247 | Console.WriteLine(overwrite.Value ? "y" : "n");
248 | Console.WriteLine();
249 | return overwrite.Value;
250 | }
251 |
252 | private void Main_Load(object sender, EventArgs e)
253 | {
254 | Control.CheckForIllegalCrossThreadCalls = false;
255 | if (!File.Exists("js.dll"))
256 | {
257 | byte[] buffer = Properties.Resources.js;
258 | string path = AppDomain.CurrentDomain.BaseDirectory + "js.dll";
259 | FileStream FS = new FileStream(path, FileMode.Create);
260 | BinaryWriter BWriter = new BinaryWriter(FS);
261 | BWriter.Write(buffer, 0, buffer.Length);
262 | BWriter.Close();
263 | }
264 |
265 | if (!File.Exists("js32.dll"))
266 | {
267 | byte[] buffer = Properties.Resources.js32;
268 | string path = AppDomain.CurrentDomain.BaseDirectory + "js32.dll";
269 | FileStream FS = new FileStream(path, FileMode.Create);
270 | BinaryWriter BWriter = new BinaryWriter(FS);
271 | BWriter.Write(buffer, 0, buffer.Length);
272 | BWriter.Close();
273 | }
274 |
275 | if (!File.Exists("libgpac.dll"))
276 | {
277 | byte[] buffer = Properties.Resources.libgpac;
278 | string path = AppDomain.CurrentDomain.BaseDirectory + "libgpac.dll";
279 | FileStream FS = new FileStream(path, FileMode.Create);
280 | BinaryWriter BWriter = new BinaryWriter(FS);
281 | BWriter.Write(buffer, 0, buffer.Length);
282 | BWriter.Close();
283 | }
284 |
285 | if (!File.Exists("MP4Box.exe"))
286 | {
287 | byte[] buffer = Properties.Resources.MP4Box;
288 | string path = AppDomain.CurrentDomain.BaseDirectory + "MP4Box.exe";
289 | FileStream FS = new FileStream(path, FileMode.Create);
290 | BinaryWriter BWriter = new BinaryWriter(FS);
291 | BWriter.Write(buffer, 0, buffer.Length);
292 | BWriter.Close();
293 | }
294 | }
295 |
296 | private void listBox1_DragDrop(object sender, DragEventArgs e)
297 | {
298 | if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
299 | {
300 | String[] files = (String[])e.Data.GetData(DataFormats.FileDrop);
301 | foreach (String s in files)
302 | {
303 | if(s.Substring(s.Length - 4, 4) != ".flv" && s.Substring(s.Length - 4, 4) != ".FLV")
304 | {
305 | MessageBox.Show("您拖入的文件 " + s + " 并不是FLV文件,请检查!", "温馨提示");
306 | }
307 | else
308 | {
309 | if (s.Contains("•"))
310 | {
311 | MessageBox.Show("文件路径中发现非法字符\"•\",请重命名 " + s + " 后重试!", "温馨提示");
312 | listBox1.Items.Clear();
313 | break;
314 | }
315 | (sender as ListBox).Items.Add(s);
316 | }
317 | }
318 | }
319 | if (listBox1.Items.Count == 0)
320 | {
321 | button4.Enabled = false;
322 | }
323 | else
324 | {
325 | button4.Enabled = true;
326 | }
327 | }
328 |
329 | private void listBox1_DragEnter(object sender, DragEventArgs e)
330 | {
331 | e.Effect = DragDropEffects.All;
332 | }
333 |
334 | private void listBox1_DragOver(object sender, DragEventArgs e)
335 | {
336 | e.Effect = DragDropEffects.All;
337 | }
338 |
339 | private void richTextBox1_TextChanged(object sender, EventArgs e)
340 | {
341 | richTextBox1.SelectionStart = richTextBox1.Text.Length;
342 | richTextBox1.ScrollToCaret();
343 | }
344 |
345 | private void Main_FormClosed(object sender, FormClosedEventArgs e)
346 | {
347 | File.Delete("js.dll");
348 | File.Delete("js32.dll");
349 | File.Delete("libgpac.dll");
350 | File.Delete("MP4Box.exe");
351 | }
352 | }
353 | }
354 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Main.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using System.Windows.Forms;
6 |
7 | namespace QiyiFLV2MP4_GUI
8 | {
9 | static class Program
10 | {
11 | ///
12 | /// 应用程序的主入口点。
13 | ///
14 | [STAThread]
15 | static void Main()
16 | {
17 | Application.EnableVisualStyles();
18 | Application.SetCompatibleTextRenderingDefault(false);
19 | Application.Run(new Main());
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("爱奇艺FLV重封装工具")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("爱奇艺FLV重封装工具")]
13 | [assembly: AssemblyCopyright("Copyright © 风漠兮 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | //将 ComVisible 设置为 false 将使此程序集中的类型
18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("aa4c7964-3baa-4eab-830b-f35194ecba88")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
33 | // 方法是按如下所示使用“*”: :
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.1.0")]
36 | [assembly: AssemblyFileVersion("1.0.1.0")]
37 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace QiyiFLV2MP4_GUI.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QiyiFLV2MP4_GUI.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 使用此强类型资源类,为所有资源查找
51 | /// 重写当前线程的 CurrentUICulture 属性。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找 System.Byte[] 类型的本地化资源。
65 | ///
66 | internal static byte[] js {
67 | get {
68 | object obj = ResourceManager.GetObject("js", resourceCulture);
69 | return ((byte[])(obj));
70 | }
71 | }
72 |
73 | ///
74 | /// 查找 System.Byte[] 类型的本地化资源。
75 | ///
76 | internal static byte[] js32 {
77 | get {
78 | object obj = ResourceManager.GetObject("js32", resourceCulture);
79 | return ((byte[])(obj));
80 | }
81 | }
82 |
83 | ///
84 | /// 查找 System.Byte[] 类型的本地化资源。
85 | ///
86 | internal static byte[] libgpac {
87 | get {
88 | object obj = ResourceManager.GetObject("libgpac", resourceCulture);
89 | return ((byte[])(obj));
90 | }
91 | }
92 |
93 | ///
94 | /// 查找 System.Byte[] 类型的本地化资源。
95 | ///
96 | internal static byte[] MP4Box {
97 | get {
98 | object obj = ResourceManager.GetObject("MP4Box", resourceCulture);
99 | return ((byte[])(obj));
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Library\js.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
123 |
124 |
125 | ..\Library\js32.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
126 |
127 |
128 | ..\Library\libgpac.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
129 |
130 |
131 | ..\Library\MP4Box.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
132 |
133 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace QiyiFLV2MP4_GUI.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4 GUI/QiyiFLV2MP4 GUI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}
8 | WinExe
9 | Properties
10 | QiyiFLV2MP4_GUI
11 | QiyiFLV2MP4 GUI
12 | v4.5.2
13 | 512
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | Form
54 |
55 |
56 | Main.cs
57 |
58 |
59 |
60 |
61 | Main.cs
62 |
63 |
64 | ResXFileCodeGenerator
65 | Resources.Designer.cs
66 | Designer
67 |
68 |
69 | True
70 | Resources.resx
71 | True
72 |
73 |
74 | SettingsSingleFileGenerator
75 | Settings.Designer.cs
76 |
77 |
78 | True
79 | Settings.settings
80 | True
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
106 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QiyiFLV2MP4", "QiyiFLV2MP4\QiyiFLV2MP4.csproj", "{A964CB68-E117-4CB7-B526-CD6076AAE07E}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QiyiFLV2MP4 GUI", "QiyiFLV2MP4 GUI\QiyiFLV2MP4 GUI.csproj", "{AA4C7964-3BAA-4EAB-830B-F35194ECBA88}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {AA4C7964-3BAA-4EAB-830B-F35194ECBA88}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/FLVFile.cs:
--------------------------------------------------------------------------------
1 | // ****************************************************************************
2 | //
3 | // FLV Extract
4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com)
5 | //
6 | // This program is free software; you can redistribute it and/or modify
7 | // it under the terms of the GNU General Public License as published by
8 | // the Free Software Foundation; either version 2 of the License, or
9 | // (at your option) any later version.
10 | //
11 | // This program is distributed in the hope that it will be useful,
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | // GNU General Public License for more details.
15 | //
16 | // You should have received a copy of the GNU General Public License
17 | // along with this program; if not, write to the Free Software
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | //
20 | // ****************************************************************************
21 |
22 | using System;
23 | using System.Collections.Generic;
24 | using System.IO;
25 | using System.Text;
26 |
27 | namespace QiyiFLV2MP4
28 | {
29 | interface IAudioWriter {
30 | void WriteChunk(byte[] chunk, uint timeStamp);
31 | void Finish();
32 | string Path { get; }
33 | }
34 |
35 | interface IVideoWriter {
36 | void WriteChunk(byte[] chunk, uint timeStamp, int frameType);
37 | void Finish(FractionUInt32 averageFrameRate);
38 | string Path { get; }
39 | }
40 |
41 | public delegate bool OverwriteDelegate(string destPath);
42 |
43 | public class FLVFile : IDisposable {
44 | static readonly string[] _outputExtensions = new string[] { ".avi", ".mp3", ".264", ".aac", ".spx", ".txt" };
45 |
46 | string _inputPath, _outputDirectory, _outputPathBase;
47 | OverwriteDelegate _overwrite;
48 | FileStream _fs;
49 | long _fileOffset, _fileLength;
50 | IAudioWriter _audioWriter;
51 | IVideoWriter _videoWriter;
52 | TimeCodeWriter _timeCodeWriter;
53 | List _videoTimeStamps;
54 | bool _extractAudio, _extractVideo, _extractTimeCodes;
55 | bool _extractedAudio, _extractedVideo, _extractedTimeCodes;
56 | FractionUInt32? _averageFrameRate, _trueFrameRate;
57 | List _warnings;
58 |
59 | public FLVFile(string path) {
60 | _inputPath = path;
61 | _outputDirectory = Path.GetDirectoryName(path);
62 | _warnings = new List();
63 | _fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 65536);
64 | _fileOffset = 0;
65 | _fileLength = _fs.Length;
66 | }
67 |
68 | public void Dispose() {
69 | if (_fs != null) {
70 | _fs.Close();
71 | _fs = null;
72 | }
73 | CloseOutput(null, true);
74 | }
75 |
76 | public void Close() {
77 | Dispose();
78 | }
79 |
80 | public string OutputDirectory {
81 | get { return _outputDirectory; }
82 | set { _outputDirectory = value; }
83 | }
84 |
85 | public FractionUInt32? AverageFrameRate {
86 | get { return _averageFrameRate; }
87 | }
88 |
89 | public FractionUInt32? TrueFrameRate {
90 | get { return _trueFrameRate; }
91 | }
92 |
93 | public string[] Warnings {
94 | get { return _warnings.ToArray(); }
95 | }
96 |
97 | public bool ExtractedAudio {
98 | get { return _extractedAudio; }
99 | }
100 |
101 | public bool ExtractedVideo {
102 | get { return _extractedVideo; }
103 | }
104 |
105 | public bool ExtractedTimeCodes {
106 | get { return _extractedTimeCodes; }
107 | }
108 |
109 | public void ExtractStreams(bool extractAudio, bool extractVideo, bool extractTimeCodes, OverwriteDelegate overwrite) {
110 | uint dataOffset, flags, prevTagSize;
111 |
112 | _outputPathBase = Path.Combine(_outputDirectory, Path.GetFileNameWithoutExtension(_inputPath));
113 | _overwrite = overwrite;
114 | _extractAudio = extractAudio;
115 | _extractVideo = extractVideo;
116 | _extractTimeCodes = extractTimeCodes;
117 | _videoTimeStamps = new List();
118 |
119 | Seek(0);
120 | if (_fileLength < 4 || ReadUInt32() != 0x464C5601) {
121 | if (_fileLength >= 8 && ReadUInt32() == 0x66747970) {
122 | throw new Exception("This is a MP4 file. Please input a FLV file!");
123 | }
124 | else {
125 | throw new Exception("This isn't a FLV file. Please input a FLV file!");
126 | }
127 | }
128 |
129 | if (Array.IndexOf(_outputExtensions, Path.GetExtension(_inputPath).ToLowerInvariant()) != -1) {
130 | // Can't have the same extension as files we output
131 | throw new Exception("Please change the extension of this FLV file.");
132 | }
133 |
134 | if (!Directory.Exists(_outputDirectory)) {
135 | throw new Exception("Output directory doesn't exist.");
136 | }
137 |
138 | flags = ReadUInt8();
139 | dataOffset = ReadUInt32();
140 |
141 | Seek(dataOffset);
142 |
143 | prevTagSize = ReadUInt32();
144 | while (_fileOffset < _fileLength) {
145 | if (!ReadTag()) break;
146 | if ((_fileLength - _fileOffset) < 4) break;
147 | prevTagSize = ReadUInt32();
148 | }
149 |
150 | _averageFrameRate = CalculateAverageFrameRate();
151 | _trueFrameRate = CalculateTrueFrameRate();
152 |
153 | CloseOutput(_averageFrameRate, false);
154 | }
155 |
156 | private void CloseOutput(FractionUInt32? averageFrameRate, bool disposing) {
157 | if (_videoWriter != null) {
158 | _videoWriter.Finish(averageFrameRate ?? new FractionUInt32(25, 1));
159 | if (disposing && (_videoWriter.Path != null)) {
160 | try { File.Delete(_videoWriter.Path); }
161 | catch { }
162 | }
163 | _videoWriter = null;
164 | }
165 | if (_audioWriter != null) {
166 | _audioWriter.Finish();
167 | if (disposing && (_audioWriter.Path != null)) {
168 | try { File.Delete(_audioWriter.Path); }
169 | catch { }
170 | }
171 | _audioWriter = null;
172 | }
173 | if (_timeCodeWriter != null) {
174 | _timeCodeWriter.Finish();
175 | if (disposing && (_timeCodeWriter.Path != null)) {
176 | try { File.Delete(_timeCodeWriter.Path); }
177 | catch { }
178 | }
179 | _timeCodeWriter = null;
180 | }
181 | }
182 |
183 | private bool ReadTag() {
184 | uint tagType, dataSize, timeStamp, streamID, mediaInfo;
185 | byte[] data;
186 |
187 | if ((_fileLength - _fileOffset) < 11) {
188 | return false;
189 | }
190 |
191 | // Read tag header
192 | tagType = ReadUInt8();
193 | dataSize = ReadUInt24();
194 | timeStamp = ReadUInt24();
195 | timeStamp |= ReadUInt8() << 24;
196 | streamID = ReadUInt24();
197 |
198 | // Read tag data
199 | if (dataSize == 0) {
200 | return true;
201 | }
202 | if ((_fileLength - _fileOffset) < dataSize) {
203 | return false;
204 | }
205 | mediaInfo = ReadUInt8();
206 | dataSize -= 1;
207 | data = ReadBytes((int)dataSize);
208 |
209 | if (tagType == 0x8) { // Audio
210 | if (_audioWriter == null) {
211 | _audioWriter = _extractAudio ? GetAudioWriter(mediaInfo) : new DummyAudioWriter();
212 | _extractedAudio = !(_audioWriter is DummyAudioWriter);
213 | }
214 | _audioWriter.WriteChunk(data, timeStamp);
215 | }
216 | else if ((tagType == 0x9) && ((mediaInfo >> 4) != 5)) { // Video
217 | if (_videoWriter == null) {
218 | _videoWriter = _extractVideo ? GetVideoWriter(mediaInfo) : new DummyVideoWriter();
219 | _extractedVideo = !(_videoWriter is DummyVideoWriter);
220 | }
221 | if (_timeCodeWriter == null) {
222 | string path = _outputPathBase + ".txt";
223 | _timeCodeWriter = new TimeCodeWriter((_extractTimeCodes && CanWriteTo(path)) ? path : null);
224 | _extractedTimeCodes = _extractTimeCodes;
225 | }
226 | _videoTimeStamps.Add(timeStamp);
227 | _videoWriter.WriteChunk(data, timeStamp, (int)((mediaInfo & 0xF0) >> 4));
228 | _timeCodeWriter.Write(timeStamp);
229 | }
230 |
231 | return true;
232 | }
233 |
234 | private IAudioWriter GetAudioWriter(uint mediaInfo) {
235 | uint format = mediaInfo >> 4;
236 | uint rate = (mediaInfo >> 2) & 0x3;
237 | uint bits = (mediaInfo >> 1) & 0x1;
238 | uint chans = mediaInfo & 0x1;
239 | string path;
240 |
241 | if ((format == 2) || (format == 14)) { // MP3
242 | path = _outputPathBase + ".mp3";
243 | if (!CanWriteTo(path)) return new DummyAudioWriter();
244 | return new MP3Writer(path, _warnings);
245 | }
246 | else if ((format == 0) || (format == 3)) { // PCM
247 | int sampleRate = 0;
248 | switch (rate) {
249 | case 0: sampleRate = 5512; break;
250 | case 1: sampleRate = 11025; break;
251 | case 2: sampleRate = 22050; break;
252 | case 3: sampleRate = 44100; break;
253 | }
254 | path = _outputPathBase + ".wav";
255 | if (!CanWriteTo(path)) return new DummyAudioWriter();
256 | if (format == 0) {
257 | _warnings.Add("PCM byte order unspecified, assuming little endian.");
258 | }
259 | return new WAVWriter(path, (bits == 1) ? 16 : 8,
260 | (chans == 1) ? 2 : 1, sampleRate);
261 | }
262 | else if (format == 10) { // AAC
263 | path = _outputPathBase + ".aac";
264 | if (!CanWriteTo(path)) return new DummyAudioWriter();
265 | return new AACWriter(path);
266 | }
267 | else if (format == 11) { // Speex
268 | path = _outputPathBase + ".spx";
269 | if (!CanWriteTo(path)) return new DummyAudioWriter();
270 | return new SpeexWriter(path, (int)(_fileLength & 0xFFFFFFFF));
271 | }
272 | else {
273 | string typeStr;
274 |
275 | if (format == 1)
276 | typeStr = "ADPCM";
277 | else if ((format == 4) || (format == 5) || (format == 6))
278 | typeStr = "Nellymoser";
279 | else
280 | typeStr = "format=" + format.ToString();
281 |
282 | _warnings.Add("Unable to extract audio (" + typeStr + " is unsupported).");
283 |
284 | return new DummyAudioWriter();
285 | }
286 | }
287 |
288 | private IVideoWriter GetVideoWriter(uint mediaInfo) {
289 | uint codecID = mediaInfo & 0x0F;
290 | string path;
291 |
292 | if ((codecID == 2) || (codecID == 4) || (codecID == 5)) {
293 | path = _outputPathBase + ".avi";
294 | if (!CanWriteTo(path)) return new DummyVideoWriter();
295 | return new AVIWriter(path, (int)codecID, _warnings);
296 | }
297 | else if (codecID == 7) {
298 | path = _outputPathBase + ".264";
299 | if (!CanWriteTo(path)) return new DummyVideoWriter();
300 | return new RawH264Writer(path);
301 | }
302 | else {
303 | string typeStr;
304 |
305 | if (codecID == 3)
306 | typeStr = "Screen";
307 | else if (codecID == 6)
308 | typeStr = "Screen2";
309 | else
310 | typeStr = "codecID=" + codecID.ToString();
311 |
312 | _warnings.Add("Unable to extract video (" + typeStr + " is unsupported).");
313 |
314 | return new DummyVideoWriter();
315 | }
316 | }
317 |
318 | private bool CanWriteTo(string path) {
319 | if (File.Exists(path) && (_overwrite != null)) {
320 | return _overwrite(path);
321 | }
322 | return true;
323 | }
324 |
325 | private FractionUInt32? CalculateAverageFrameRate() {
326 | FractionUInt32 frameRate;
327 | int frameCount = _videoTimeStamps.Count;
328 |
329 | if (frameCount > 1) {
330 | frameRate.N = (uint)(frameCount - 1) * 1000;
331 | frameRate.D = _videoTimeStamps[frameCount - 1] - _videoTimeStamps[0];
332 | frameRate.Reduce();
333 | return frameRate;
334 | }
335 | else {
336 | return null;
337 | }
338 | }
339 |
340 | private FractionUInt32? CalculateTrueFrameRate() {
341 | FractionUInt32 frameRate;
342 | Dictionary deltaCount = new Dictionary();
343 | int i, threshold;
344 | uint delta, count, minDelta;
345 |
346 | // Calculate the distance between the timestamps, count how many times each delta appears
347 | for (i = 1; i < _videoTimeStamps.Count; i++) {
348 | int deltaS = (int)((long)_videoTimeStamps[i] - (long)_videoTimeStamps[i - 1]);
349 |
350 | if (deltaS <= 0) continue;
351 | delta = (uint)deltaS;
352 |
353 | if (deltaCount.ContainsKey(delta)) {
354 | deltaCount[delta] += 1;
355 | }
356 | else {
357 | deltaCount.Add(delta, 1);
358 | }
359 | }
360 |
361 | threshold = _videoTimeStamps.Count / 10;
362 | minDelta = UInt32.MaxValue;
363 |
364 | // Find the smallest delta that made up at least 10% of the frames (grouping in delta+1
365 | // because of rounding, e.g. a NTSC video will have deltas of 33 and 34 ms)
366 | foreach (KeyValuePair deltaItem in deltaCount) {
367 | delta = deltaItem.Key;
368 | count = deltaItem.Value;
369 |
370 | if (deltaCount.ContainsKey(delta + 1)) {
371 | count += deltaCount[delta + 1];
372 | }
373 |
374 | if ((count >= threshold) && (delta < minDelta)) {
375 | minDelta = delta;
376 | }
377 | }
378 |
379 | // Calculate the frame rate based on the smallest delta, and delta+1 if present
380 | if (minDelta != UInt32.MaxValue) {
381 | uint totalTime, totalFrames;
382 |
383 | count = deltaCount[minDelta];
384 | totalTime = minDelta * count;
385 | totalFrames = count;
386 |
387 | if (deltaCount.ContainsKey(minDelta + 1)) {
388 | count = deltaCount[minDelta + 1];
389 | totalTime += (minDelta + 1) * count;
390 | totalFrames += count;
391 | }
392 |
393 | if (totalTime != 0) {
394 | frameRate.N = totalFrames * 1000;
395 | frameRate.D = totalTime;
396 | frameRate.Reduce();
397 | return frameRate;
398 | }
399 | }
400 |
401 | // Unable to calculate frame rate
402 | return null;
403 | }
404 |
405 | private void Seek(long offset) {
406 | _fs.Seek(offset, SeekOrigin.Begin);
407 | _fileOffset = offset;
408 | }
409 |
410 | private uint ReadUInt8() {
411 | _fileOffset += 1;
412 | return (uint)_fs.ReadByte();
413 | }
414 |
415 | private uint ReadUInt24() {
416 | byte[] x = new byte[4];
417 | _fs.Read(x, 1, 3);
418 | _fileOffset += 3;
419 | return BitConverterBE.ToUInt32(x, 0);
420 | }
421 |
422 | private uint ReadUInt32() {
423 | byte[] x = new byte[4];
424 | _fs.Read(x, 0, 4);
425 | _fileOffset += 4;
426 | return BitConverterBE.ToUInt32(x, 0);
427 | }
428 |
429 | private byte[] ReadBytes(int length) {
430 | byte[] buff = new byte[length];
431 | _fs.Read(buff, 0, length);
432 | _fileOffset += length;
433 | return buff;
434 | }
435 | }
436 |
437 | class DummyAudioWriter : IAudioWriter {
438 | public DummyAudioWriter() {
439 | }
440 |
441 | public void WriteChunk(byte[] chunk, uint timeStamp) {
442 | }
443 |
444 | public void Finish() {
445 | }
446 |
447 | public string Path {
448 | get {
449 | return null;
450 | }
451 | }
452 | }
453 |
454 | class DummyVideoWriter : IVideoWriter {
455 | public DummyVideoWriter() {
456 | }
457 |
458 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) {
459 | }
460 |
461 | public void Finish(FractionUInt32 averageFrameRate) {
462 | }
463 |
464 | public string Path {
465 | get {
466 | return null;
467 | }
468 | }
469 | }
470 |
471 | class MP3Writer : IAudioWriter {
472 | string _path;
473 | FileStream _fs;
474 | List _warnings;
475 | List _chunkBuffer;
476 | List _frameOffsets;
477 | uint _totalFrameLength;
478 | bool _isVBR;
479 | bool _delayWrite;
480 | bool _hasVBRHeader;
481 | bool _writeVBRHeader;
482 | int _firstBitRate;
483 | int _mpegVersion;
484 | int _sampleRate;
485 | int _channelMode;
486 | uint _firstFrameHeader;
487 |
488 | public MP3Writer(string path, List warnings) {
489 | _path = path;
490 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
491 | _warnings = warnings;
492 | _chunkBuffer = new List();
493 | _frameOffsets = new List();
494 | _delayWrite = true;
495 | }
496 |
497 | public void WriteChunk(byte[] chunk, uint timeStamp) {
498 | _chunkBuffer.Add(chunk);
499 | ParseMP3Frames(chunk);
500 | if (_delayWrite && _totalFrameLength >= 65536) {
501 | _delayWrite = false;
502 | }
503 | if (!_delayWrite) {
504 | Flush();
505 | }
506 | }
507 |
508 | public void Finish() {
509 | Flush();
510 | if (_writeVBRHeader) {
511 | _fs.Seek(0, SeekOrigin.Begin);
512 | WriteVBRHeader(false);
513 | }
514 | _fs.Close();
515 | }
516 |
517 | public string Path {
518 | get {
519 | return _path;
520 | }
521 | }
522 |
523 | private void Flush() {
524 | foreach (byte[] chunk in _chunkBuffer) {
525 | _fs.Write(chunk, 0, chunk.Length);
526 | }
527 | _chunkBuffer.Clear();
528 | }
529 |
530 | private void ParseMP3Frames(byte[] buff) {
531 | int[] MPEG1BitRate = new int[] { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 };
532 | int[] MPEG2XBitRate = new int[] { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 };
533 | int[] MPEG1SampleRate = new int[] { 44100, 48000, 32000, 0 };
534 | int[] MPEG20SampleRate = new int[] { 22050, 24000, 16000, 0 };
535 | int[] MPEG25SampleRate = new int[] { 11025, 12000, 8000, 0 };
536 |
537 | int offset = 0;
538 | int length = buff.Length;
539 |
540 | while (length >= 4) {
541 | ulong header;
542 | int mpegVersion, layer, bitRate, sampleRate, padding, channelMode;
543 | int frameLen;
544 |
545 | header = (ulong)BitConverterBE.ToUInt32(buff, offset) << 32;
546 | if (BitHelper.Read(ref header, 11) != 0x7FF) {
547 | break;
548 | }
549 | mpegVersion = BitHelper.Read(ref header, 2);
550 | layer = BitHelper.Read(ref header, 2);
551 | BitHelper.Read(ref header, 1);
552 | bitRate = BitHelper.Read(ref header, 4);
553 | sampleRate = BitHelper.Read(ref header, 2);
554 | padding = BitHelper.Read(ref header, 1);
555 | BitHelper.Read(ref header, 1);
556 | channelMode = BitHelper.Read(ref header, 2);
557 |
558 | if ((mpegVersion == 1) || (layer != 1) || (bitRate == 0) || (bitRate == 15) || (sampleRate == 3)) {
559 | break;
560 | }
561 |
562 | bitRate = ((mpegVersion == 3) ? MPEG1BitRate[bitRate] : MPEG2XBitRate[bitRate]) * 1000;
563 |
564 | if (mpegVersion == 3)
565 | sampleRate = MPEG1SampleRate[sampleRate];
566 | else if (mpegVersion == 2)
567 | sampleRate = MPEG20SampleRate[sampleRate];
568 | else
569 | sampleRate = MPEG25SampleRate[sampleRate];
570 |
571 | frameLen = GetFrameLength(mpegVersion, bitRate, sampleRate, padding);
572 | if (frameLen > length) {
573 | break;
574 | }
575 |
576 | bool isVBRHeaderFrame = false;
577 | if (_frameOffsets.Count == 0) {
578 | // Check for an existing VBR header just to be safe (I haven't seen any in FLVs)
579 | int o = offset + GetFrameDataOffset(mpegVersion, channelMode);
580 | if (BitConverterBE.ToUInt32(buff, o) == 0x58696E67) { // "Xing"
581 | isVBRHeaderFrame = true;
582 | _delayWrite = false;
583 | _hasVBRHeader = true;
584 | }
585 | }
586 |
587 | if (isVBRHeaderFrame) { }
588 | else if (_firstBitRate == 0) {
589 | _firstBitRate = bitRate;
590 | _mpegVersion = mpegVersion;
591 | _sampleRate = sampleRate;
592 | _channelMode = channelMode;
593 | _firstFrameHeader = BitConverterBE.ToUInt32(buff, offset);
594 | }
595 | else if (!_isVBR && (bitRate != _firstBitRate)) {
596 | _isVBR = true;
597 | if (_hasVBRHeader) { }
598 | else if (_delayWrite) {
599 | WriteVBRHeader(true);
600 | _writeVBRHeader = true;
601 | _delayWrite = false;
602 | }
603 | else {
604 | _warnings.Add("Detected VBR too late, cannot add VBR header.");
605 | }
606 | }
607 |
608 | _frameOffsets.Add(_totalFrameLength + (uint)offset);
609 |
610 | offset += frameLen;
611 | length -= frameLen;
612 | }
613 |
614 | _totalFrameLength += (uint)buff.Length;
615 | }
616 |
617 | private void WriteVBRHeader(bool isPlaceholder) {
618 | byte[] buff = new byte[GetFrameLength(_mpegVersion, 64000, _sampleRate, 0)];
619 | if (!isPlaceholder) {
620 | uint header = _firstFrameHeader;
621 | int dataOffset = GetFrameDataOffset(_mpegVersion, _channelMode);
622 | header &= 0xFFFF0DFF; // Clear bitrate and padding fields
623 | header |= 0x00010000; // Set protection bit (indicates that CRC is NOT present)
624 | header |= (uint)((_mpegVersion == 3) ? 5 : 8) << 12; // 64 kbit/sec
625 | General.CopyBytes(buff, 0, BitConverterBE.GetBytes(header));
626 | General.CopyBytes(buff, dataOffset, BitConverterBE.GetBytes((uint)0x58696E67)); // "Xing"
627 | General.CopyBytes(buff, dataOffset + 4, BitConverterBE.GetBytes((uint)0x7)); // Flags
628 | General.CopyBytes(buff, dataOffset + 8, BitConverterBE.GetBytes((uint)_frameOffsets.Count)); // Frame count
629 | General.CopyBytes(buff, dataOffset + 12, BitConverterBE.GetBytes((uint)_totalFrameLength)); // File length
630 | for (int i = 0; i < 100; i++) {
631 | int frameIndex = (int)((i / 100.0) * _frameOffsets.Count);
632 | buff[dataOffset + 16 + i] = (byte)((_frameOffsets[frameIndex] / (double)_totalFrameLength) * 256.0);
633 | }
634 | }
635 | _fs.Write(buff, 0, buff.Length);
636 | }
637 |
638 | private int GetFrameLength(int mpegVersion, int bitRate, int sampleRate, int padding) {
639 | return ((mpegVersion == 3) ? 144 : 72) * bitRate / sampleRate + padding;
640 | }
641 |
642 | private int GetFrameDataOffset(int mpegVersion, int channelMode) {
643 | return 4 + ((mpegVersion == 3) ?
644 | ((channelMode == 3) ? 17 : 32) :
645 | ((channelMode == 3) ? 9 : 17));
646 | }
647 | }
648 |
649 | class SpeexWriter : IAudioWriter {
650 | const string _vendorString = "FLV Extract";
651 | const uint _sampleRate = 16000;
652 | const uint _msPerFrame = 20;
653 | const uint _samplesPerFrame = _sampleRate / (1000 / _msPerFrame);
654 | const int _targetPageDataSize = 4096;
655 |
656 | string _path;
657 | FileStream _fs;
658 | int _serialNumber;
659 | List _packetList;
660 | int _packetListDataSize;
661 | byte[] _pageBuff;
662 | int _pageBuffOffset;
663 | uint _pageSequenceNumber;
664 | ulong _granulePosition;
665 |
666 | public SpeexWriter(string path, int serialNumber) {
667 | _path = path;
668 | _serialNumber = serialNumber;
669 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
670 | _fs.Seek((28 + 80) + (28 + 8 + _vendorString.Length), SeekOrigin.Begin); // Speex header + Vorbis comment
671 | _packetList = new List();
672 | _packetListDataSize = 0;
673 | _pageBuff = new byte[27 + 255 + _targetPageDataSize + 254]; // Header + max segment table + target data size + extra segment
674 | _pageBuffOffset = 0;
675 | _pageSequenceNumber = 2; // First audio packet
676 | _granulePosition = 0;
677 | }
678 |
679 | public void WriteChunk(byte[] chunk, uint timeStamp) {
680 | int[] subModeSizes = new int[] { 0, 43, 119, 160, 220, 300, 364, 492, 79 };
681 | int[] wideBandSizes = new int[] { 0, 36, 112, 192, 352 };
682 | int[] inBandSignalSizes = new int[] { 1, 1, 4, 4, 4, 4, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 };
683 | int frameStart = -1;
684 | int frameEnd = 0;
685 | int offset = 0;
686 | int length = chunk.Length * 8;
687 | int x;
688 |
689 | while (length - offset >= 5) {
690 | x = BitHelper.Read(chunk, ref offset, 1);
691 | if (x != 0) {
692 | // wideband frame
693 | x = BitHelper.Read(chunk, ref offset, 3);
694 | if (x < 1 || x > 4) goto Error;
695 | offset += wideBandSizes[x] - 4;
696 | }
697 | else {
698 | x = BitHelper.Read(chunk, ref offset, 4);
699 | if (x >= 1 && x <= 8) {
700 | // narrowband frame
701 | if (frameStart != -1) {
702 | WriteFramePacket(chunk, frameStart, frameEnd);
703 | }
704 | frameStart = frameEnd;
705 | offset += subModeSizes[x] - 5;
706 | }
707 | else if (x == 15) {
708 | // terminator
709 | break;
710 | }
711 | else if (x == 14) {
712 | // in-band signal
713 | if (length - offset < 4) goto Error;
714 | x = BitHelper.Read(chunk, ref offset, 4);
715 | offset += inBandSignalSizes[x];
716 | }
717 | else if (x == 13) {
718 | // custom in-band signal
719 | if (length - offset < 5) goto Error;
720 | x = BitHelper.Read(chunk, ref offset, 5);
721 | offset += x * 8;
722 | }
723 | else goto Error;
724 | }
725 | frameEnd = offset;
726 | }
727 | if (offset > length) goto Error;
728 |
729 | if (frameStart != -1) {
730 | WriteFramePacket(chunk, frameStart, frameEnd);
731 | }
732 |
733 | return;
734 |
735 | Error:
736 | throw new Exception("Invalid Speex data.");
737 | }
738 |
739 | public void Finish() {
740 | WritePage();
741 | FlushPage(true);
742 | _fs.Seek(0, SeekOrigin.Begin);
743 | _pageSequenceNumber = 0;
744 | _granulePosition = 0;
745 | WriteSpeexHeaderPacket();
746 | WriteVorbisCommentPacket();
747 | FlushPage(false);
748 | _fs.Close();
749 | }
750 |
751 | public string Path {
752 | get {
753 | return _path;
754 | }
755 | }
756 |
757 | private void WriteFramePacket(byte[] data, int startBit, int endBit) {
758 | int lengthBits = endBit - startBit;
759 | byte[] frame = BitHelper.CopyBlock(data, startBit, lengthBits);
760 | if (lengthBits % 8 != 0) {
761 | frame[frame.Length - 1] |= (byte)(0xFF >> ((lengthBits % 8) + 1)); // padding
762 | }
763 | AddPacket(frame, _samplesPerFrame, true);
764 | }
765 |
766 | private void WriteSpeexHeaderPacket() {
767 | byte[] data = new byte[80];
768 | General.CopyBytes(data, 0, Encoding.ASCII.GetBytes("Speex ")); // speex_string
769 | General.CopyBytes(data, 8, Encoding.ASCII.GetBytes("unknown")); // speex_version
770 | data[28] = 1; // speex_version_id
771 | data[32] = 80; // header_size
772 | General.CopyBytes(data, 36, BitConverterLE.GetBytes((uint)_sampleRate)); // rate
773 | data[40] = 1; // mode (e.g. narrowband, wideband)
774 | data[44] = 4; // mode_bitstream_version
775 | data[48] = 1; // nb_channels
776 | General.CopyBytes(data, 52, BitConverterLE.GetBytes(unchecked((uint)-1))); // bitrate
777 | General.CopyBytes(data, 56, BitConverterLE.GetBytes((uint)_samplesPerFrame)); // frame_size
778 | data[60] = 0; // vbr
779 | data[64] = 1; // frames_per_packet
780 | AddPacket(data, 0, false);
781 | }
782 |
783 | private void WriteVorbisCommentPacket() {
784 | byte[] vendorStringBytes = Encoding.ASCII.GetBytes(_vendorString);
785 | byte[] data = new byte[8 + vendorStringBytes.Length];
786 | data[0] = (byte)vendorStringBytes.Length;
787 | General.CopyBytes(data, 4, vendorStringBytes);
788 | AddPacket(data, 0, false);
789 | }
790 |
791 | private void AddPacket(byte[] data, uint sampleLength, bool delayWrite) {
792 | OggPacket packet = new OggPacket();
793 | if (data.Length >= 255) {
794 | throw new Exception("Packet exceeds maximum size.");
795 | }
796 | _granulePosition += sampleLength;
797 | packet.Data = data;
798 | packet.GranulePosition = _granulePosition;
799 | _packetList.Add(packet);
800 | _packetListDataSize += data.Length;
801 | if (!delayWrite || (_packetListDataSize >= _targetPageDataSize) || (_packetList.Count == 255)) {
802 | WritePage();
803 | }
804 | }
805 |
806 | private void WritePage() {
807 | if (_packetList.Count == 0) return;
808 | FlushPage(false);
809 | WriteToPage(BitConverterBE.GetBytes(0x4F676753U), 0, 4); // "OggS"
810 | WriteToPage((byte)0); // Stream structure version
811 | WriteToPage((byte)((_pageSequenceNumber == 0) ? 0x02 : 0)); // Page flags
812 | WriteToPage((ulong)_packetList[_packetList.Count - 1].GranulePosition); // Position in samples
813 | WriteToPage((uint)_serialNumber); // Stream serial number
814 | WriteToPage((uint)_pageSequenceNumber); // Page sequence number
815 | WriteToPage((uint)0); // Checksum
816 | WriteToPage((byte)_packetList.Count); // Page segment count
817 | foreach (OggPacket packet in _packetList) {
818 | WriteToPage((byte)packet.Data.Length);
819 | }
820 | foreach (OggPacket packet in _packetList) {
821 | WriteToPage(packet.Data, 0, packet.Data.Length);
822 | }
823 | _packetList.Clear();
824 | _packetListDataSize = 0;
825 | _pageSequenceNumber++;
826 | }
827 |
828 | private void FlushPage(bool isLastPage) {
829 | if (_pageBuffOffset == 0) return;
830 | if (isLastPage) _pageBuff[5] |= 0x04;
831 | uint crc = OggCRC.Calculate(_pageBuff, 0, _pageBuffOffset);
832 | General.CopyBytes(_pageBuff, 22, BitConverterLE.GetBytes(crc));
833 | _fs.Write(_pageBuff, 0, _pageBuffOffset);
834 | _pageBuffOffset = 0;
835 | }
836 |
837 | private void WriteToPage(byte[] data, int offset, int length) {
838 | Buffer.BlockCopy(data, offset, _pageBuff, _pageBuffOffset, length);
839 | _pageBuffOffset += length;
840 | }
841 |
842 | private void WriteToPage(byte data) {
843 | WriteToPage(new byte[] { data }, 0, 1);
844 | }
845 |
846 | private void WriteToPage(uint data) {
847 | WriteToPage(BitConverterLE.GetBytes(data), 0, 4);
848 | }
849 |
850 | private void WriteToPage(ulong data) {
851 | WriteToPage(BitConverterLE.GetBytes(data), 0, 8);
852 | }
853 |
854 | class OggPacket {
855 | public ulong GranulePosition;
856 | public byte[] Data;
857 | }
858 | }
859 |
860 | class AACWriter : IAudioWriter {
861 | string _path;
862 | FileStream _fs;
863 | int _aacProfile;
864 | int _sampleRateIndex;
865 | int _channelConfig;
866 |
867 | public AACWriter(string path) {
868 | _path = path;
869 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
870 | }
871 |
872 | public void WriteChunk(byte[] chunk, uint timeStamp) {
873 | if (chunk.Length < 1) return;
874 |
875 | if (chunk[0] == 0) { // Header
876 | if (chunk.Length < 3) return;
877 |
878 | ulong bits = (ulong)BitConverterBE.ToUInt16(chunk, 1) << 48;
879 |
880 | _aacProfile = BitHelper.Read(ref bits, 5) - 1;
881 | _sampleRateIndex = BitHelper.Read(ref bits, 4);
882 | _channelConfig = BitHelper.Read(ref bits, 4);
883 |
884 | if ((_aacProfile < 0) || (_aacProfile > 3))
885 | throw new Exception("Unsupported AAC profile.");
886 | if (_sampleRateIndex > 12)
887 | throw new Exception("Invalid AAC sample rate index.");
888 | if (_channelConfig > 6)
889 | throw new Exception("Invalid AAC channel configuration.");
890 | }
891 | else { // Audio data
892 | int dataSize = chunk.Length - 1;
893 | ulong bits = 0;
894 |
895 | // Reference: WriteADTSHeader from FAAC's bitstream.c
896 |
897 | BitHelper.Write(ref bits, 12, 0xFFF);
898 | BitHelper.Write(ref bits, 1, 0);
899 | BitHelper.Write(ref bits, 2, 0);
900 | BitHelper.Write(ref bits, 1, 1);
901 | BitHelper.Write(ref bits, 2, _aacProfile);
902 | BitHelper.Write(ref bits, 4, _sampleRateIndex);
903 | BitHelper.Write(ref bits, 1, 0);
904 | BitHelper.Write(ref bits, 3, _channelConfig);
905 | BitHelper.Write(ref bits, 1, 0);
906 | BitHelper.Write(ref bits, 1, 0);
907 | BitHelper.Write(ref bits, 1, 0);
908 | BitHelper.Write(ref bits, 1, 0);
909 | BitHelper.Write(ref bits, 13, 7 + dataSize);
910 | BitHelper.Write(ref bits, 11, 0x7FF);
911 | BitHelper.Write(ref bits, 2, 0);
912 |
913 | _fs.Write(BitConverterBE.GetBytes(bits), 1, 7);
914 | _fs.Write(chunk, 1, dataSize);
915 | }
916 | }
917 |
918 | public void Finish() {
919 | _fs.Close();
920 | }
921 |
922 | public string Path {
923 | get {
924 | return _path;
925 | }
926 | }
927 | }
928 |
929 | class RawH264Writer : IVideoWriter {
930 | static readonly byte[] _startCode = new byte[] { 0, 0, 0, 1 };
931 |
932 | string _path;
933 | FileStream _fs;
934 | int _nalLengthSize;
935 |
936 | public RawH264Writer(string path) {
937 | _path = path;
938 | _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
939 | }
940 |
941 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) {
942 | if (chunk.Length < 4) return;
943 |
944 | // Reference: decode_frame from libavcodec's h264.c
945 |
946 | if (chunk[0] == 0) { // Headers
947 | if (chunk.Length < 10) return;
948 |
949 | int offset, spsCount, ppsCount;
950 |
951 | offset = 8;
952 | _nalLengthSize = (chunk[offset++] & 0x03) + 1;
953 | spsCount = chunk[offset++] & 0x1F;
954 | ppsCount = -1;
955 |
956 | while (offset <= chunk.Length - 2) {
957 | if ((spsCount == 0) && (ppsCount == -1)) {
958 | ppsCount = chunk[offset++];
959 | continue;
960 | }
961 |
962 | if (spsCount > 0) spsCount--;
963 | else if (ppsCount > 0) ppsCount--;
964 | else break;
965 |
966 | int len = (int)BitConverterBE.ToUInt16(chunk, offset);
967 | offset += 2;
968 | if (offset + len > chunk.Length) break;
969 | _fs.Write(_startCode, 0, _startCode.Length);
970 | _fs.Write(chunk, offset, len);
971 | offset += len;
972 | }
973 | }
974 | else { // Video data
975 | int offset = 4;
976 |
977 | if (_nalLengthSize != 2) {
978 | _nalLengthSize = 4;
979 | }
980 |
981 | while (offset <= chunk.Length - _nalLengthSize) {
982 | int len = (_nalLengthSize == 2) ?
983 | (int)BitConverterBE.ToUInt16(chunk, offset) :
984 | (int)BitConverterBE.ToUInt32(chunk, offset);
985 | offset += _nalLengthSize;
986 | if (offset + len > chunk.Length) break;
987 | _fs.Write(_startCode, 0, _startCode.Length);
988 | _fs.Write(chunk, offset, len);
989 | offset += len;
990 | }
991 | }
992 | }
993 |
994 | public void Finish(FractionUInt32 averageFrameRate) {
995 | _fs.Close();
996 | }
997 |
998 | public string Path {
999 | get {
1000 | return _path;
1001 | }
1002 | }
1003 | }
1004 |
1005 | class WAVWriter : IAudioWriter {
1006 | string _path;
1007 | WAVTools.WAVWriter _wr;
1008 | int blockAlign;
1009 |
1010 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) {
1011 | _path = path;
1012 | _wr = new WAVTools.WAVWriter(path, bitsPerSample, channelCount, sampleRate);
1013 | blockAlign = (bitsPerSample / 8) * channelCount;
1014 | }
1015 |
1016 | public void WriteChunk(byte[] chunk, uint timeStamp) {
1017 | _wr.Write(chunk, chunk.Length / blockAlign);
1018 | }
1019 |
1020 | public void Finish() {
1021 | _wr.Close();
1022 | }
1023 |
1024 | public string Path {
1025 | get {
1026 | return _path;
1027 | }
1028 | }
1029 | }
1030 |
1031 | class AVIWriter : IVideoWriter {
1032 | string _path;
1033 | BinaryWriter _bw;
1034 | int _codecID;
1035 | int _width, _height, _frameCount;
1036 | uint _moviDataSize, _indexChunkSize;
1037 | List _index;
1038 | bool _isAlphaWriter;
1039 | AVIWriter _alphaWriter;
1040 | List _warnings;
1041 |
1042 | // Chunk: Off: Len:
1043 | //
1044 | // RIFF AVI 0 12
1045 | // LIST hdrl 12 12
1046 | // avih 24 64
1047 | // LIST strl 88 12
1048 | // strh 100 64
1049 | // strf 164 48
1050 | // LIST movi 212 12
1051 | // (frames) 224 ???
1052 | // idx1 ??? ???
1053 |
1054 | public AVIWriter(string path, int codecID, List warnings) :
1055 | this(path, codecID, warnings, false) { }
1056 |
1057 | private AVIWriter(string path, int codecID, List warnings, bool isAlphaWriter) {
1058 | if ((codecID != 2) && (codecID != 4) && (codecID != 5)) {
1059 | throw new Exception("Unsupported video codec.");
1060 | }
1061 |
1062 | FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
1063 |
1064 | _path = path;
1065 | _bw = new BinaryWriter(fs);
1066 | _codecID = codecID;
1067 | _warnings = warnings;
1068 | _isAlphaWriter = isAlphaWriter;
1069 |
1070 | if ((codecID == 5) && !_isAlphaWriter) {
1071 | _alphaWriter = new AVIWriter(path.Substring(0, path.Length - 4) + ".alpha.avi", codecID, warnings, true);
1072 | }
1073 |
1074 | WriteFourCC("RIFF");
1075 | _bw.Write((uint)0); // chunk size
1076 | WriteFourCC("AVI ");
1077 |
1078 | WriteFourCC("LIST");
1079 | _bw.Write((uint)192);
1080 | WriteFourCC("hdrl");
1081 |
1082 | WriteFourCC("avih");
1083 | _bw.Write((uint)56);
1084 | _bw.Write((uint)0);
1085 | _bw.Write((uint)0);
1086 | _bw.Write((uint)0);
1087 | _bw.Write((uint)0x10);
1088 | _bw.Write((uint)0); // frame count
1089 | _bw.Write((uint)0);
1090 | _bw.Write((uint)1);
1091 | _bw.Write((uint)0);
1092 | _bw.Write((uint)0); // width
1093 | _bw.Write((uint)0); // height
1094 | _bw.Write((uint)0);
1095 | _bw.Write((uint)0);
1096 | _bw.Write((uint)0);
1097 | _bw.Write((uint)0);
1098 |
1099 | WriteFourCC("LIST");
1100 | _bw.Write((uint)116);
1101 | WriteFourCC("strl");
1102 |
1103 | WriteFourCC("strh");
1104 | _bw.Write((uint)56);
1105 | WriteFourCC("vids");
1106 | WriteFourCC(CodecFourCC);
1107 | _bw.Write((uint)0);
1108 | _bw.Write((uint)0);
1109 | _bw.Write((uint)0);
1110 | _bw.Write((uint)0); // frame rate denominator
1111 | _bw.Write((uint)0); // frame rate numerator
1112 | _bw.Write((uint)0);
1113 | _bw.Write((uint)0); // frame count
1114 | _bw.Write((uint)0);
1115 | _bw.Write((int)-1);
1116 | _bw.Write((uint)0);
1117 | _bw.Write((ushort)0);
1118 | _bw.Write((ushort)0);
1119 | _bw.Write((ushort)0); // width
1120 | _bw.Write((ushort)0); // height
1121 |
1122 | WriteFourCC("strf");
1123 | _bw.Write((uint)40);
1124 | _bw.Write((uint)40);
1125 | _bw.Write((uint)0); // width
1126 | _bw.Write((uint)0); // height
1127 | _bw.Write((ushort)1);
1128 | _bw.Write((ushort)24);
1129 | WriteFourCC(CodecFourCC);
1130 | _bw.Write((uint)0); // biSizeImage
1131 | _bw.Write((uint)0);
1132 | _bw.Write((uint)0);
1133 | _bw.Write((uint)0);
1134 | _bw.Write((uint)0);
1135 |
1136 | WriteFourCC("LIST");
1137 | _bw.Write((uint)0); // chunk size
1138 | WriteFourCC("movi");
1139 |
1140 | _index = new List();
1141 | }
1142 |
1143 | public void WriteChunk(byte[] chunk, uint timeStamp, int frameType) {
1144 | int offset, len;
1145 |
1146 | offset = 0;
1147 | len = chunk.Length;
1148 | if (_codecID == 4) {
1149 | offset = 1;
1150 | len -= 1;
1151 | }
1152 | if (_codecID == 5) {
1153 | offset = 4;
1154 | if (len >= 4) {
1155 | int alphaOffset = (int)BitConverterBE.ToUInt32(chunk, 0) & 0xFFFFFF;
1156 | if (!_isAlphaWriter) {
1157 | len = alphaOffset;
1158 | }
1159 | else {
1160 | offset += alphaOffset;
1161 | len -= offset;
1162 | }
1163 | }
1164 | else {
1165 | len = 0;
1166 | }
1167 | }
1168 | len = Math.Max(len, 0);
1169 | len = Math.Min(len, chunk.Length - offset);
1170 |
1171 | _index.Add((frameType == 1) ? (uint)0x10 : (uint)0);
1172 | _index.Add(_moviDataSize + 4);
1173 | _index.Add((uint)len);
1174 |
1175 | if ((_width == 0) && (_height == 0)) {
1176 | GetFrameSize(chunk);
1177 | }
1178 |
1179 | WriteFourCC("00dc");
1180 | _bw.Write(len);
1181 | _bw.Write(chunk, offset, len);
1182 |
1183 | if ((len % 2) != 0) {
1184 | _bw.Write((byte)0);
1185 | len++;
1186 | }
1187 | _moviDataSize += (uint)len + 8;
1188 | _frameCount++;
1189 |
1190 | if (_alphaWriter != null) {
1191 | _alphaWriter.WriteChunk(chunk, timeStamp, frameType);
1192 | }
1193 | }
1194 |
1195 | private void GetFrameSize(byte[] chunk) {
1196 | if (_codecID == 2) {
1197 | // Reference: flv_h263_decode_picture_header from libavcodec's h263.c
1198 |
1199 | if (chunk.Length < 10) return;
1200 |
1201 | if ((chunk[0] != 0) || (chunk[1] != 0)) {
1202 | return;
1203 | }
1204 |
1205 | ulong x = BitConverterBE.ToUInt64(chunk, 2);
1206 | int format;
1207 |
1208 | if (BitHelper.Read(ref x, 1) != 1) {
1209 | return;
1210 | }
1211 | BitHelper.Read(ref x, 5);
1212 | BitHelper.Read(ref x, 8);
1213 |
1214 | format = BitHelper.Read(ref x, 3);
1215 | switch (format) {
1216 | case 0:
1217 | _width = BitHelper.Read(ref x, 8);
1218 | _height = BitHelper.Read(ref x, 8);
1219 | break;
1220 | case 1:
1221 | _width = BitHelper.Read(ref x, 16);
1222 | _height = BitHelper.Read(ref x, 16);
1223 | break;
1224 | case 2:
1225 | _width = 352;
1226 | _height = 288;
1227 | break;
1228 | case 3:
1229 | _width = 176;
1230 | _height = 144;
1231 | break;
1232 | case 4:
1233 | _width = 128;
1234 | _height = 96;
1235 | break;
1236 | case 5:
1237 | _width = 320;
1238 | _height = 240;
1239 | break;
1240 | case 6:
1241 | _width = 160;
1242 | _height = 120;
1243 | break;
1244 | default:
1245 | return;
1246 | }
1247 | }
1248 | else if ((_codecID == 4) || (_codecID == 5)) {
1249 | // Reference: vp6_parse_header from libavcodec's vp6.c
1250 |
1251 | int skip = (_codecID == 4) ? 1 : 4;
1252 | if (chunk.Length < (skip + 8)) return;
1253 | ulong x = BitConverterBE.ToUInt64(chunk, skip);
1254 |
1255 | int deltaFrameFlag = BitHelper.Read(ref x, 1);
1256 | int quant = BitHelper.Read(ref x, 6);
1257 | int separatedCoeffFlag = BitHelper.Read(ref x, 1);
1258 | int subVersion = BitHelper.Read(ref x, 5);
1259 | int filterHeader = BitHelper.Read(ref x, 2);
1260 | int interlacedFlag = BitHelper.Read(ref x, 1);
1261 |
1262 | if (deltaFrameFlag != 0) {
1263 | return;
1264 | }
1265 | if ((separatedCoeffFlag != 0) || (filterHeader == 0)) {
1266 | BitHelper.Read(ref x, 16);
1267 | }
1268 |
1269 | _height = BitHelper.Read(ref x, 8) * 16;
1270 | _width = BitHelper.Read(ref x, 8) * 16;
1271 |
1272 | // chunk[0] contains the width and height (4 bits each, respectively) that should
1273 | // be cropped off during playback, which will be non-zero if the encoder padded
1274 | // the frames to a macroblock boundary. But if you use this adjusted size in the
1275 | // AVI header, DirectShow seems to ignore it, and it can cause stride or chroma
1276 | // alignment problems with VFW if the width/height aren't multiples of 4.
1277 | if (!_isAlphaWriter) {
1278 | int cropX = chunk[0] >> 4;
1279 | int cropY = chunk[0] & 0x0F;
1280 | if (((cropX != 0) || (cropY != 0)) && !_isAlphaWriter) {
1281 | _warnings.Add(String.Format("Suggested cropping: {0} pixels from right, {1} pixels from bottom.", cropX, cropY));
1282 | }
1283 | }
1284 | }
1285 | }
1286 |
1287 | private string CodecFourCC {
1288 | get {
1289 | if (_codecID == 2) {
1290 | return "FLV1";
1291 | }
1292 | if ((_codecID == 4) || (_codecID == 5)) {
1293 | return "VP6F";
1294 | }
1295 | return "NULL";
1296 | }
1297 | }
1298 |
1299 | private void WriteIndexChunk() {
1300 | uint indexDataSize = (uint)_frameCount * 16;
1301 |
1302 | WriteFourCC("idx1");
1303 | _bw.Write(indexDataSize);
1304 |
1305 | for (int i = 0; i < _frameCount; i++) {
1306 | WriteFourCC("00dc");
1307 | _bw.Write(_index[(i * 3) + 0]);
1308 | _bw.Write(_index[(i * 3) + 1]);
1309 | _bw.Write(_index[(i * 3) + 2]);
1310 | }
1311 |
1312 | _indexChunkSize = indexDataSize + 8;
1313 | }
1314 |
1315 | public void Finish(FractionUInt32 averageFrameRate) {
1316 | WriteIndexChunk();
1317 |
1318 | _bw.BaseStream.Seek(4, SeekOrigin.Begin);
1319 | _bw.Write((uint)(224 + _moviDataSize + _indexChunkSize - 8));
1320 |
1321 | _bw.BaseStream.Seek(24 + 8, SeekOrigin.Begin);
1322 | _bw.Write((uint)0);
1323 | _bw.BaseStream.Seek(12, SeekOrigin.Current);
1324 | _bw.Write((uint)_frameCount);
1325 | _bw.BaseStream.Seek(12, SeekOrigin.Current);
1326 | _bw.Write((uint)_width);
1327 | _bw.Write((uint)_height);
1328 |
1329 | _bw.BaseStream.Seek(100 + 28, SeekOrigin.Begin);
1330 | _bw.Write((uint)averageFrameRate.D);
1331 | _bw.Write((uint)averageFrameRate.N);
1332 | _bw.BaseStream.Seek(4, SeekOrigin.Current);
1333 | _bw.Write((uint)_frameCount);
1334 | _bw.BaseStream.Seek(16, SeekOrigin.Current);
1335 | _bw.Write((ushort)_width);
1336 | _bw.Write((ushort)_height);
1337 |
1338 | _bw.BaseStream.Seek(164 + 12, SeekOrigin.Begin);
1339 | _bw.Write((uint)_width);
1340 | _bw.Write((uint)_height);
1341 | _bw.BaseStream.Seek(8, SeekOrigin.Current);
1342 | _bw.Write((uint)(_width * _height * 6));
1343 |
1344 | _bw.BaseStream.Seek(212 + 4, SeekOrigin.Begin);
1345 | _bw.Write((uint)(_moviDataSize + 4));
1346 |
1347 | _bw.Close();
1348 |
1349 | if (_alphaWriter != null) {
1350 | _alphaWriter.Finish(averageFrameRate);
1351 | }
1352 | }
1353 |
1354 | private void WriteFourCC(string fourCC) {
1355 | byte[] bytes = System.Text.Encoding.ASCII.GetBytes(fourCC);
1356 | if (bytes.Length != 4) {
1357 | throw new Exception("Invalid FourCC length.");
1358 | }
1359 | _bw.Write(bytes);
1360 | }
1361 |
1362 | public string Path {
1363 | get {
1364 | return _path;
1365 | }
1366 | }
1367 | }
1368 |
1369 | class TimeCodeWriter {
1370 | string _path;
1371 | StreamWriter _sw;
1372 |
1373 | public TimeCodeWriter(string path) {
1374 | _path = path;
1375 | if (path != null) {
1376 | _sw = new StreamWriter(path, false, Encoding.ASCII);
1377 | _sw.WriteLine("# timecode format v2");
1378 | }
1379 | }
1380 |
1381 | public void Write(uint timeStamp) {
1382 | if (_sw != null) {
1383 | _sw.WriteLine(timeStamp);
1384 | }
1385 | }
1386 |
1387 | public void Finish() {
1388 | if (_sw != null) {
1389 | _sw.Close();
1390 | _sw = null;
1391 | }
1392 | }
1393 |
1394 | public string Path {
1395 | get {
1396 | return _path;
1397 | }
1398 | }
1399 | }
1400 |
1401 | public struct FractionUInt32 {
1402 | public uint N;
1403 | public uint D;
1404 |
1405 | public FractionUInt32(uint n, uint d) {
1406 | N = n;
1407 | D = d;
1408 | }
1409 |
1410 | public double ToDouble() {
1411 | return (double)N / (double)D;
1412 | }
1413 |
1414 | public void Reduce() {
1415 | uint gcd = GCD(N, D);
1416 | N /= gcd;
1417 | D /= gcd;
1418 | }
1419 |
1420 | private uint GCD(uint a, uint b) {
1421 | uint r;
1422 |
1423 | while (b != 0) {
1424 | r = a % b;
1425 | a = b;
1426 | b = r;
1427 | }
1428 |
1429 | return a;
1430 | }
1431 |
1432 | public override string ToString() {
1433 | return ToString(true);
1434 | }
1435 |
1436 | public string ToString(bool full) {
1437 | if (full) {
1438 | return ToDouble().ToString() + " (" + N.ToString() + "/" + D.ToString() + ")";
1439 | }
1440 | else {
1441 | return ToDouble().ToString("0.####");
1442 | }
1443 | }
1444 | }
1445 | }
1446 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/General.cs:
--------------------------------------------------------------------------------
1 | // ****************************************************************************
2 | //
3 | // FLV Extract
4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com)
5 | //
6 | // This program is free software; you can redistribute it and/or modify
7 | // it under the terms of the GNU General Public License as published by
8 | // the Free Software Foundation; either version 2 of the License, or
9 | // (at your option) any later version.
10 | //
11 | // This program is distributed in the hope that it will be useful,
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | // GNU General Public License for more details.
15 | //
16 | // You should have received a copy of the GNU General Public License
17 | // along with this program; if not, write to the Free Software
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | //
20 | // ****************************************************************************
21 |
22 | using System;
23 | using System.Reflection;
24 |
25 | namespace QiyiFLV2MP4
26 | {
27 | public static class General {
28 | public static string Version {
29 | get {
30 | Version ver = Assembly.GetExecutingAssembly().GetName().Version;
31 | return ver.Major + "." + ver.Minor + "." + ver.Revision;
32 | }
33 | }
34 |
35 | public static void CopyBytes(byte[] dst, int dstOffset, byte[] src) {
36 | Buffer.BlockCopy(src, 0, dst, dstOffset, src.Length);
37 | }
38 | }
39 |
40 | public static class BitHelper {
41 | public static int Read(ref ulong x, int length) {
42 | int r = (int)(x >> (64 - length));
43 | x <<= length;
44 | return r;
45 | }
46 |
47 | public static int Read(byte[] bytes, ref int offset, int length) {
48 | int startByte = offset / 8;
49 | int endByte = (offset + length - 1) / 8;
50 | int skipBits = offset % 8;
51 | ulong bits = 0;
52 | for (int i = 0; i <= Math.Min(endByte - startByte, 7); i++) {
53 | bits |= (ulong)bytes[startByte + i] << (56 - (i * 8));
54 | }
55 | if (skipBits != 0) Read(ref bits, skipBits);
56 | offset += length;
57 | return Read(ref bits, length);
58 | }
59 |
60 | public static void Write(ref ulong x, int length, int value) {
61 | ulong mask = 0xFFFFFFFFFFFFFFFF >> (64 - length);
62 | x = (x << length) | ((ulong)value & mask);
63 | }
64 |
65 | public static byte[] CopyBlock(byte[] bytes, int offset, int length) {
66 | int startByte = offset / 8;
67 | int endByte = (offset + length - 1) / 8;
68 | int shiftA = offset % 8;
69 | int shiftB = 8 - shiftA;
70 | byte[] dst = new byte[(length + 7) / 8];
71 | if (shiftA == 0) {
72 | Buffer.BlockCopy(bytes, startByte, dst, 0, dst.Length);
73 | }
74 | else {
75 | int i;
76 | for (i = 0; i < endByte - startByte; i++) {
77 | dst[i] = (byte)((bytes[startByte + i] << shiftA) | (bytes[startByte + i + 1] >> shiftB));
78 | }
79 | if (i < dst.Length) {
80 | dst[i] = (byte)(bytes[startByte + i] << shiftA);
81 | }
82 | }
83 | dst[dst.Length - 1] &= (byte)(0xFF << ((dst.Length * 8) - length));
84 | return dst;
85 | }
86 | }
87 |
88 | public static class BitConverterBE {
89 | public static ulong ToUInt64(byte[] value, int startIndex) {
90 | return
91 | ((ulong)value[startIndex ] << 56) |
92 | ((ulong)value[startIndex + 1] << 48) |
93 | ((ulong)value[startIndex + 2] << 40) |
94 | ((ulong)value[startIndex + 3] << 32) |
95 | ((ulong)value[startIndex + 4] << 24) |
96 | ((ulong)value[startIndex + 5] << 16) |
97 | ((ulong)value[startIndex + 6] << 8) |
98 | ((ulong)value[startIndex + 7] );
99 | }
100 |
101 | public static uint ToUInt32(byte[] value, int startIndex) {
102 | return
103 | ((uint)value[startIndex ] << 24) |
104 | ((uint)value[startIndex + 1] << 16) |
105 | ((uint)value[startIndex + 2] << 8) |
106 | ((uint)value[startIndex + 3] );
107 | }
108 |
109 | public static ushort ToUInt16(byte[] value, int startIndex) {
110 | return (ushort)(
111 | (value[startIndex ] << 8) |
112 | (value[startIndex + 1] ));
113 | }
114 |
115 | public static byte[] GetBytes(ulong value) {
116 | byte[] buff = new byte[8];
117 | buff[0] = (byte)(value >> 56);
118 | buff[1] = (byte)(value >> 48);
119 | buff[2] = (byte)(value >> 40);
120 | buff[3] = (byte)(value >> 32);
121 | buff[4] = (byte)(value >> 24);
122 | buff[5] = (byte)(value >> 16);
123 | buff[6] = (byte)(value >> 8);
124 | buff[7] = (byte)(value );
125 | return buff;
126 | }
127 |
128 | public static byte[] GetBytes(uint value) {
129 | byte[] buff = new byte[4];
130 | buff[0] = (byte)(value >> 24);
131 | buff[1] = (byte)(value >> 16);
132 | buff[2] = (byte)(value >> 8);
133 | buff[3] = (byte)(value );
134 | return buff;
135 | }
136 |
137 | public static byte[] GetBytes(ushort value) {
138 | byte[] buff = new byte[2];
139 | buff[0] = (byte)(value >> 8);
140 | buff[1] = (byte)(value );
141 | return buff;
142 | }
143 | }
144 |
145 | public static class BitConverterLE {
146 | public static byte[] GetBytes(ulong value) {
147 | byte[] buff = new byte[8];
148 | buff[0] = (byte)(value );
149 | buff[1] = (byte)(value >> 8);
150 | buff[2] = (byte)(value >> 16);
151 | buff[3] = (byte)(value >> 24);
152 | buff[4] = (byte)(value >> 32);
153 | buff[5] = (byte)(value >> 40);
154 | buff[6] = (byte)(value >> 48);
155 | buff[7] = (byte)(value >> 56);
156 | return buff;
157 | }
158 |
159 | public static byte[] GetBytes(uint value) {
160 | byte[] buff = new byte[4];
161 | buff[0] = (byte)(value );
162 | buff[1] = (byte)(value >> 8);
163 | buff[2] = (byte)(value >> 16);
164 | buff[3] = (byte)(value >> 24);
165 | return buff;
166 | }
167 |
168 | public static byte[] GetBytes(ushort value) {
169 | byte[] buff = new byte[2];
170 | buff[0] = (byte)(value );
171 | buff[1] = (byte)(value >> 8);
172 | return buff;
173 | }
174 | }
175 |
176 | public static class OggCRC {
177 | static uint[] _lut = new uint[256];
178 |
179 | static OggCRC() {
180 | for (uint i = 0; i < 256; i++) {
181 | uint x = i << 24;
182 | for (uint j = 0; j < 8; j++) {
183 | x = ((x & 0x80000000U) != 0) ? ((x << 1) ^ 0x04C11DB7) : (x << 1);
184 | }
185 | _lut[i] = x;
186 | }
187 | }
188 |
189 | public static uint Calculate(byte[] buff, int offset, int length) {
190 | uint crc = 0;
191 | for (int i = 0; i < length; i++) {
192 | crc = _lut[((crc >> 24) ^ buff[offset + i]) & 0xFF] ^ (crc << 8);
193 | }
194 | return crc;
195 | }
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/MP4Box.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/MP4Box.exe
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/WAVFile.cs:
--------------------------------------------------------------------------------
1 | // ****************************************************************************
2 | //
3 | // FLV Extract
4 | // Copyright (C) 2006-2011 J.D. Purcell (moitah@yahoo.com)
5 | //
6 | // This program is free software; you can redistribute it and/or modify
7 | // it under the terms of the GNU General Public License as published by
8 | // the Free Software Foundation; either version 2 of the License, or
9 | // (at your option) any later version.
10 | //
11 | // This program is distributed in the hope that it will be useful,
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | // GNU General Public License for more details.
15 | //
16 | // You should have received a copy of the GNU General Public License
17 | // along with this program; if not, write to the Free Software
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 | //
20 | // ****************************************************************************
21 |
22 | using System;
23 | using System.IO;
24 |
25 | namespace WAVTools {
26 | public class WAVWriter {
27 | BinaryWriter _bw;
28 | bool _canSeek;
29 | bool _wroteHeaders;
30 | int _bitsPerSample, _channelCount, _sampleRate, _blockAlign;
31 | long _finalSampleLen, _sampleLen;
32 |
33 | public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) :
34 | this(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read),
35 | bitsPerSample, channelCount, sampleRate)
36 | {
37 | }
38 |
39 | public WAVWriter(Stream stream, int bitsPerSample, int channelCount, int sampleRate) {
40 | _bitsPerSample = bitsPerSample;
41 | _channelCount = channelCount;
42 | _sampleRate = sampleRate;
43 | _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8);
44 |
45 | _bw = new BinaryWriter(stream);
46 | _canSeek = stream.CanSeek;
47 | }
48 |
49 | private void WriteHeaders() {
50 | const uint fccRIFF = 0x46464952;
51 | const uint fccWAVE = 0x45564157;
52 | const uint fccFormat = 0x20746D66;
53 | const uint fccData = 0x61746164;
54 |
55 | uint dataChunkSize = GetDataChunkSize(_finalSampleLen);
56 |
57 | _bw.Write(fccRIFF);
58 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36));
59 | _bw.Write(fccWAVE);
60 |
61 | _bw.Write(fccFormat);
62 | _bw.Write((uint)16);
63 | _bw.Write((ushort)1);
64 | _bw.Write((ushort)_channelCount);
65 | _bw.Write((uint)_sampleRate);
66 | _bw.Write((uint)(_sampleRate * _blockAlign));
67 | _bw.Write((ushort)_blockAlign);
68 | _bw.Write((ushort)_bitsPerSample);
69 |
70 | _bw.Write(fccData);
71 | _bw.Write((uint)dataChunkSize);
72 | }
73 |
74 | private uint GetDataChunkSize(long sampleCount) {
75 | const long maxFileSize = 0x7FFFFFFEL;
76 | long dataSize = sampleCount * _blockAlign;
77 | if ((dataSize + 44) > maxFileSize) {
78 | dataSize = ((maxFileSize - 44) / _blockAlign) * _blockAlign;
79 | }
80 | return (uint)dataSize;
81 | }
82 |
83 | public void Close() {
84 | if (((_sampleLen * _blockAlign) & 1) == 1) {
85 | _bw.Write((byte)0);
86 | }
87 |
88 | try {
89 | if (_sampleLen != _finalSampleLen) {
90 | if (_canSeek) {
91 | uint dataChunkSize = GetDataChunkSize(_sampleLen);
92 | _bw.Seek(4, SeekOrigin.Begin);
93 | _bw.Write((uint)(dataChunkSize + (dataChunkSize & 1) + 36));
94 | _bw.Seek(40, SeekOrigin.Begin);
95 | _bw.Write((uint)dataChunkSize);
96 | }
97 | else {
98 | throw new Exception("Samples written differs from the expected sample count.");
99 | }
100 | }
101 | }
102 | finally {
103 | _bw.Close();
104 | _bw = null;
105 | }
106 | }
107 |
108 | public long Position {
109 | get {
110 | return _sampleLen;
111 | }
112 | }
113 |
114 | public long FinalSampleCount {
115 | set {
116 | _finalSampleLen = value;
117 | }
118 | }
119 |
120 | public void Write(byte[] buff, int sampleCount) {
121 | if (sampleCount <= 0) return;
122 |
123 | if (!_wroteHeaders) {
124 | WriteHeaders();
125 | _wroteHeaders = true;
126 | }
127 |
128 | _bw.Write(buff, 0, sampleCount * _blockAlign);
129 | _sampleLen += sampleCount;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/js.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/js.dll
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/js32.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/js32.dll
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Library/libgpac.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fengmoxi/QiyiFLV2MP4/6cd43191ef38def7c2afaf75a6c67385a8b8c3a0/QiyiFLV2MP4/Library/libgpac.dll
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Diagnostics;
8 |
9 | namespace QiyiFLV2MP4
10 | {
11 | class Program
12 | {
13 | static bool _autoOverwrite;
14 | private static StringBuilder sortOutput = null;
15 | public static string inputPath = "";
16 | static int Main(string[] args)
17 | {
18 | int argCount = args.Length;
19 | int argIndex = 0;
20 | bool extractVideo = false;
21 | bool extractAudio = false;
22 | bool extractTimeCodes = false;
23 | string outputDirectory = null;
24 |
25 | Console.WriteLine();
26 | Console.WriteLine("QiyiFLV2MP4 v" + General.Version);
27 | Console.WriteLine("Copyright 2016 风漠兮");
28 | Console.WriteLine("http://www.fengmoxi.com/");
29 | Console.WriteLine();
30 |
31 | if (!File.Exists("js.dll"))
32 | {
33 | byte[] buffer = Properties.Resources.js;
34 | string path = AppDomain.CurrentDomain.BaseDirectory + "js.dll";
35 | FileStream FS = new FileStream(path, FileMode.Create);
36 | BinaryWriter BWriter = new BinaryWriter(FS);
37 | BWriter.Write(buffer, 0, buffer.Length);
38 | BWriter.Close();
39 | }
40 |
41 | if (!File.Exists("js32.dll"))
42 | {
43 | byte[] buffer = Properties.Resources.js32;
44 | string path = AppDomain.CurrentDomain.BaseDirectory + "js32.dll";
45 | FileStream FS = new FileStream(path, FileMode.Create);
46 | BinaryWriter BWriter = new BinaryWriter(FS);
47 | BWriter.Write(buffer, 0, buffer.Length);
48 | BWriter.Close();
49 | }
50 |
51 | if (!File.Exists("libgpac.dll"))
52 | {
53 | byte[] buffer = Properties.Resources.libgpac;
54 | string path = AppDomain.CurrentDomain.BaseDirectory + "libgpac.dll";
55 | FileStream FS = new FileStream(path, FileMode.Create);
56 | BinaryWriter BWriter = new BinaryWriter(FS);
57 | BWriter.Write(buffer, 0, buffer.Length);
58 | BWriter.Close();
59 | }
60 |
61 | if (!File.Exists("MP4Box.exe"))
62 | {
63 | byte[] buffer = Properties.Resources.MP4Box;
64 | string path = AppDomain.CurrentDomain.BaseDirectory + "MP4Box.exe";
65 | FileStream FS = new FileStream(path, FileMode.Create);
66 | BinaryWriter BWriter = new BinaryWriter(FS);
67 | BWriter.Write(buffer, 0, buffer.Length);
68 | BWriter.Close();
69 | }
70 |
71 | try
72 | {
73 | _autoOverwrite = true;
74 | extractVideo = true;
75 | extractAudio = true;
76 |
77 | if (argIndex != (argCount - 1))
78 | {
79 | throw new Exception();
80 | }
81 | inputPath = args[argIndex];
82 | }
83 | catch
84 | {
85 | Console.WriteLine("Arguments: source_path");
86 | Console.WriteLine();
87 | return 1;
88 | }
89 |
90 | try
91 | {
92 | using (FLVFile flvFile = new FLVFile(Path.GetFullPath(inputPath)))
93 | {
94 | if (outputDirectory != null)
95 | {
96 | flvFile.OutputDirectory = Path.GetFullPath(outputDirectory);
97 | }
98 | flvFile.ExtractStreams(extractAudio, extractVideo, extractTimeCodes, PromptOverwrite);
99 | if ((flvFile.TrueFrameRate != null) || (flvFile.AverageFrameRate != null))
100 | {
101 | if (flvFile.TrueFrameRate != null)
102 | {
103 | Console.WriteLine("True Frame Rate: " + flvFile.TrueFrameRate.ToString());
104 | }
105 | if (flvFile.AverageFrameRate != null)
106 | {
107 | Console.WriteLine("Average Frame Rate: " + flvFile.AverageFrameRate.ToString());
108 | }
109 | Console.WriteLine();
110 | }
111 | if (flvFile.Warnings.Length != 0)
112 | {
113 | foreach (string warning in flvFile.Warnings)
114 | {
115 | Console.WriteLine("Warning: " + warning);
116 | }
117 | Console.WriteLine();
118 | }
119 | }
120 | }
121 | catch (Exception ex)
122 | {
123 | Console.WriteLine("Error: " + ex.Message);
124 | return 1;
125 | }
126 |
127 | Process sortProcess;
128 | sortProcess = new Process();
129 | sortOutput = new StringBuilder("");
130 | sortProcess.StartInfo.FileName = "cmd.exe";
131 | sortProcess.StartInfo.UseShellExecute = false;// 必须禁用操作系统外壳程序
132 | sortProcess.StartInfo.RedirectStandardOutput = true;
133 | sortProcess.StartInfo.RedirectStandardError = true; //重定向错误输出
134 | sortProcess.StartInfo.CreateNoWindow = true; //设置不显示窗口
135 | sortProcess.StartInfo.RedirectStandardInput = true;
136 | sortProcess.StartInfo.Arguments = "/c mp4box.exe -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".264#trackID=1:par=1:1:name=\" -add \"" + inputPath.Substring(0, inputPath.Length - 4) + ".aac:name=\" -new \"" + inputPath.Substring(0, inputPath.Length - 4) + ".mp4\""; //设定程式执行参数
137 | sortProcess.Start();
138 | sortProcess.BeginOutputReadLine();// 异步获取命令行内容
139 | sortProcess.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
140 | Console.WriteLine("Packaging!Please wait with patience!");
141 | sortProcess.WaitForExit();//等待程序执行完退出进程
142 | sortProcess.Close();
143 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".aac"))
144 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".aac");
145 | if (File.Exists(inputPath.Substring(0, inputPath.Length - 4) + ".264"))
146 | File.Delete(inputPath.Substring(0, inputPath.Length - 4) + ".264");
147 | Console.WriteLine("Congratulations! " + inputPath + " has been converted to " + inputPath.Substring(0, inputPath.Length - 4) + ".mp4 successfully!");
148 | return 0;
149 | }
150 |
151 | private static bool PromptOverwrite(string path)
152 | {
153 | if (_autoOverwrite) return true;
154 | bool? overwrite = null;
155 | Console.Write("Output file \"" + Path.GetFileName(path) + "\" already exists, overwrite? (y/n): ");
156 | while (overwrite == null)
157 | {
158 | char c = Console.ReadKey(true).KeyChar;
159 | if (c == 'y') overwrite = true;
160 | if (c == 'n') overwrite = false;
161 | }
162 | Console.WriteLine(overwrite.Value ? "y" : "n");
163 | Console.WriteLine();
164 | return overwrite.Value;
165 | }
166 | private static void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
167 | {
168 | if (!String.IsNullOrEmpty(outLine.Data))
169 | {
170 | Console.WriteLine(outLine.Data + Environment.NewLine);
171 | }
172 | }
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("爱奇艺FLV重封装工具")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("QiyiFLV2MP4")]
13 | [assembly: AssemblyCopyright("Copyright © 风漠兮 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | //将 ComVisible 设置为 false 将使此程序集中的类型
18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("a964cb68-e117-4cb7-b526-cd6076aae07e")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
33 | // 方法是按如下所示使用“*”: :
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.1.0")]
36 | [assembly: AssemblyFileVersion("1.0.1.0")]
37 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace QiyiFLV2MP4.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QiyiFLV2MP4.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 使用此强类型资源类,为所有资源查找
51 | /// 重写当前线程的 CurrentUICulture 属性。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找 System.Byte[] 类型的本地化资源。
65 | ///
66 | internal static byte[] js {
67 | get {
68 | object obj = ResourceManager.GetObject("js", resourceCulture);
69 | return ((byte[])(obj));
70 | }
71 | }
72 |
73 | ///
74 | /// 查找 System.Byte[] 类型的本地化资源。
75 | ///
76 | internal static byte[] js32 {
77 | get {
78 | object obj = ResourceManager.GetObject("js32", resourceCulture);
79 | return ((byte[])(obj));
80 | }
81 | }
82 |
83 | ///
84 | /// 查找 System.Byte[] 类型的本地化资源。
85 | ///
86 | internal static byte[] libgpac {
87 | get {
88 | object obj = ResourceManager.GetObject("libgpac", resourceCulture);
89 | return ((byte[])(obj));
90 | }
91 | }
92 |
93 | ///
94 | /// 查找 System.Byte[] 类型的本地化资源。
95 | ///
96 | internal static byte[] MP4Box {
97 | get {
98 | object obj = ResourceManager.GetObject("MP4Box", resourceCulture);
99 | return ((byte[])(obj));
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Library\js.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
123 |
124 |
125 | ..\Library\js32.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
126 |
127 |
128 | ..\Library\libgpac.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
129 |
130 |
131 | ..\Library\MP4Box.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
132 |
133 |
--------------------------------------------------------------------------------
/QiyiFLV2MP4/QiyiFLV2MP4.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {A964CB68-E117-4CB7-B526-CD6076AAE07E}
8 | Exe
9 | Properties
10 | QiyiFLV2MP4
11 | QiyiFLV2MP4
12 | v4.5.2
13 | 512
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | True
53 | True
54 | Resources.resx
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | ResXFileCodeGenerator
63 | Resources.Designer.cs
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
98 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #QiyiFLV2MP4
2 |
3 | QSV文件格式是奇艺影音特有的视频文件格式,尽管可以使用QSV2FLV转换成FLV格式,但是转换FLV时间轴不对,大部分常用播放器会出现时间变长、播放卡顿等现象,使用本工具可以重新封装QSV2FLV生成的FLV文件为MP4文件,以使常用播放器正常播放。
4 |
5 | 使用方法:在命令提示符中切换到FLV所在文件夹,输入QiyiFLV2MP4 文件名即可。
--------------------------------------------------------------------------------