tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ExportJob.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14ExportJob::ExportJob (Edit* edit_,
15 const juce::File& destDir_,
16 const Project::Ptr& newProject_,
17 const Project::Ptr& srcProject_,
18 TracktionArchiveFile* archive_,
19 double handleSize_,
20 bool keepEntireFiles_,
21 TracktionArchiveFile::CompressionType compressionType_,
22 juce::Array<juce::File>& filesForDeletion_,
23 juce::StringArray& failedFiles_,
24 bool includeLibraryFiles_,
25 bool includeClips_)
26 : ThreadPoolJobWithProgress (TRANS("Exporting") + "..."),
27 edit (edit_),
28 destDir (destDir_),
29 newProject (newProject_),
30 srcProject (srcProject_),
31 handleSize (handleSize_),
32 keepEntireFiles (keepEntireFiles_),
33 compressionType (compressionType_),
34 archive (archive_),
35 filesForDeletion (filesForDeletion_),
36 failedFiles (failedFiles_),
37 includeLibraryFiles (includeLibraryFiles_),
38 includeClips (includeClips_)
39{
40 jassert (srcProject != nullptr);
41 jassert (newProject != nullptr);
42}
43
44ExportJob::~ExportJob()
45{
46}
47
48//==============================================================================
49juce::ThreadPoolJob::JobStatus ExportJob::runJob()
50{
52
54
55 if (edit != nullptr)
56 copyEditFilesToTempDir();
57 else
58 copyProjectFilesToTempDir();
59
60 if (archive != nullptr && ! shouldExit())
61 createArchiveFromTempFiles();
62
63 if (shouldExit())
64 {
65 if (archive != nullptr)
66 {
67 archive->flush();
68 filesForDeletion.add (archive->getFile());
69 filesForDeletion.add (destDir);
70 }
71 else
72 {
73 destDir.findChildFiles (filesForDeletion, juce::File::findFiles, true);
74 }
75
76 failedFiles.clear();
77 }
78
79 return jobHasFinished;
80}
81
82//==============================================================================
83void ExportJob::copyProjectFilesToTempDir()
84{
85 bool showedSpaceWarning = false;
86
87 // exporting the whole project
88 auto newProjectFile = destDir.getChildFile (srcProject->getProjectFile().getFileName());
89
90 if (srcProject->getProjectFile().copyFileTo (newProjectFile))
91 {
92 newProjectFile.setReadOnly (false);
93
94 auto destProject = srcProject->projectManager.createNewProject (newProjectFile);
95 destProject->createNewProjectId();
96
97 for (int i = 0; i < destProject->getNumProjectItems(); ++i)
98 {
99 progress = ((archive != nullptr) ? 0.5f : 1.0f) * i
100 / (float) destProject->getNumProjectItems();
101
102 if (shouldExit())
103 break;
104
105 auto srcObject = srcProject->getProjectItemAt (i);
106 auto destObject = destProject->getProjectItemAt (i);
107
108 if (srcObject != nullptr && destObject != nullptr
109 && srcObject->getSourceFile().existsAsFile())
110 {
111 auto dest = destDir.getChildFile (srcObject->getSourceFile().getFileName())
112 .getNonexistentSibling (true);
113
114 auto bytesFree = dest.getBytesFreeOnVolume();
115 auto bytesNeeded = std::max (2 * srcObject->getSourceFile().getSize(),
116 (juce::int64) (1024 * 1024 * 50));
117
118 if (bytesFree > 0
119 && bytesFree < bytesNeeded
120 && ! showedSpaceWarning)
121 {
122 showedSpaceWarning = true;
123 edit->engine.getUIBehaviour().showWarningAlert (TRANS("Exporting"),
124 TRANS("Disk space is critically low!")
125 + "\n\n"
126 + TRANS("Not all files may be exported correctly."));
127 }
128
129 if (! srcObject->getSourceFile().copyFileTo (dest))
130 {
131 failedFiles.add (dest.getFileName());
132 }
133 else
134 {
135 dest.setReadOnly (false);
136 destObject->setSourceFile (dest);
137 }
138 }
139 }
140
141 callBlocking ([this, &destProject]()
142 {
143 destProject->redirectIDsFromProject (srcProject->getProjectID(), destProject->getProjectID());
144 destProject->save();
145 });
146 }
147 else
148 {
149 failedFiles.add (srcProject->getProjectFile().getFullPathName());
150 }
151}
152
153//==============================================================================
154void ExportJob::copyEditFilesToTempDir()
155{
156 jassert (edit != nullptr);
157
158 if (! includeClips)
159 {
160 for (auto t : getClipTracks (*edit))
161 {
162 for (int j = t->getClips().size(); --j >= 0;)
163 {
164 auto clip = t->getClips().getUnchecked (j);
165
166 if (clip->type != TrackItem::Type::video
167 && clip->type != TrackItem::Type::marker)
168 {
169 clip->removeFromParent();
170 }
171 }
172 }
173
174 for (auto t : getAudioTracks (*edit))
175 {
176 for (auto s : t->getClipSlotList().getClipSlots())
177 if (auto c = s->getClip())
178 c->removeFromParent();
179 }
180 }
181
182 auto allExportables = Exportable::addAllExportables (*edit);
183
184 if (keepEntireFiles)
185 handleSize = 10000.0;
186
187 auto& projectManager = srcProject->projectManager;
188 ReferencedMaterialList refList (projectManager, handleSize);
189
190 for (auto exportable : allExportables)
191 for (auto& i : exportable->getReferencedItems())
192 refList.add (i);
193
194 int numThingsCopied = 0;
195 const int totalThingsToCopy = refList.getTotalNumThingsToExport();
196
197 for (auto exportable : allExportables)
198 {
199 if (shouldExit())
200 break;
201
202 for (auto ref : exportable->getReferencedItems())
203 {
204 progress = ((archive != nullptr ? 0.5f : 1.0f) * numThingsCopied) / totalThingsToCopy;
205
206 if (shouldExit())
207 break;
208
209 double start = 0.0, length = 0.0;
210 auto newFilename = refList.getReassignedFileName (ref.itemID, ref.firstTimeUsed,
211 start, length);
212
213 if (length > 0.0 && newFilename.isNotEmpty())
214 {
215 auto oldSourceMedia (projectManager.getProjectItem (ref.itemID));
216
217 if (oldSourceMedia != nullptr
218 && (includeLibraryFiles || ! oldSourceMedia->getProject()->isLibraryProject()))
219 {
220 ProjectItem::Ptr newSourceItem;
221
222 // the file we're creating as a section of the whole thing
223 auto newFile = destDir.getChildFile (newFilename);
224
225 if (! newFile.exists())
226 {
227 juce::File actualNewFile;
228
229 if (! oldSourceMedia->copySectionToNewFile (newFile, actualNewFile, start, length))
230 {
231 failedFiles.add (oldSourceMedia->getFileName());
232 TRACKTION_LOG_ERROR ("Failed to copy file during edit archive: " + newFile.getFullPathName());
233 }
234 else
235 {
236 newFile = actualNewFile;
237
238 newSourceItem = newProject->createNewItem (newFile,
239 oldSourceMedia->getType(),
240 oldSourceMedia->getName(),
241 oldSourceMedia->getDescription(),
242 oldSourceMedia->getCategory(),
243 true);
244
245 if (newSourceItem != nullptr)
246 newSourceItem->copyAllPropertiesFrom (*oldSourceMedia);
247 }
248
249 ++numThingsCopied;
250 }
251 else
252 {
253 newSourceItem = newProject->getProjectItemForFile (newFile);
254 }
255
256 auto newID = newSourceItem != nullptr ? newSourceItem->getID() : ProjectItemID();
257
258 callBlocking ([=]() { exportable->reassignReferencedItem (ref, newID, start); });
259 }
260 }
261 else
262 {
263 // Couldn't create the new clip, so avoid pointing at the old one
264 // Create a new random ID here as it could be used to reference a comp or similar
265 callBlocking ([&]() { exportable->reassignReferencedItem (ref, ProjectItemID::createNewID (newProject->getProjectID()), start); });
266 }
267 }
268 }
269
270 // put the new edit at the top of the list
271 newProject->moveProjectItem (newProject->getIndexOf (edit->getProjectItemID()), 0);
272 callBlocking ([this] { EditFileOperations (*edit).save (true, true, false); });
273}
274
275//==============================================================================
276void ExportJob::createArchiveFromTempFiles()
277{
278 if (archive != nullptr && ! shouldExit())
279 {
280 if (newProject != nullptr)
281 {
282 newProject->save();
283 newProject->unlockFile();
284 }
285
286 destDir.findChildFiles (filesForDeletion, juce::File::findFiles, true);
287
288 for (int i = 0; i < filesForDeletion.size(); ++i)
289 {
290 progress = 0.5f + 0.5f * i / filesForDeletion.size();
291
292 if (shouldExit())
293 break;
294
295 auto compression = TracktionArchiveFile::CompressionType::zip;
296 auto f = filesForDeletion[i];
297
298 if (AudioFile (srcProject->engine, f).isValid())
299 compression = compressionType;
300
301 if (! archive->addFile (f, destDir, compression))
302 failedFiles.add (f.getFileName());
303 }
304
305 filesForDeletion.clear();
306 filesForDeletion.add (destDir);
307 }
308}
309
310//==============================================================================
311float ExportJob::getCurrentTaskProgress()
312{
313 return progress;
314}
315
316}} // namespace tracktion { inline namespace engine
static void JUCE_CALLTYPE disableDenormalisedNumberSupport(bool shouldDisable=true) noexcept
#define TRANS(stringLiteral)
#define jassert(expression)
typedef float
T max(T... args)
long long int64
juce::Array< AudioTrack * > getAudioTracks(const Edit &edit)
Returns all the AudioTracks in an Edit.
juce::Array< ClipTrack * > getClipTracks(const Edit &edit)
Returns all the ClipTracks in an Edit.
T ref(T... args)
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.