ardour/libs/aaf/ProTools.c

441 lines
15 KiB
C

/*
* Copyright (C) 2017-2024 Adrien Gesta-Fline
*
* This file is part of libAAF.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include "aaf/AAFIface.h"
#include "aaf/ProTools.h"
#define debug(...) \
AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_DEBUG, __VA_ARGS__)
#define warning(...) \
AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_WARNING, __VA_ARGS__)
#define error(...) \
AAF_LOG (aafi->log, aafi, LOG_SRC_ID_AAF_IFACE, VERB_ERROR, __VA_ARGS__)
/* English : "Fade " (Same as JA and DE) */
static const char PROTOOLS_CLIP_NAME_FADE_EN[] = "\x46\x61\x64\x65\x20";
/* French : "Fondu " */
static const char PROTOOLS_CLIP_NAME_FADE_FR[] = "\x46\x6f\x6e\x64\x75\x20";
/* Spanish : "Fundido" */
static const char PROTOOLS_CLIP_NAME_FADE_ES[] = "\x46\x75\x6e\x64\x69\x64\x6f\x20";
/* Korean : "페이드" */
static const char PROTOOLS_CLIP_NAME_FADE_KO[] = "\xed\x8e\x98\xec\x9d\xb4\xeb\x93\x9c";
/* Chinese (S) : "淡变 " */
static const char PROTOOLS_CLIP_NAME_FADE_ZH_CN[] = "\xe6\xb7\xa1\xe5\x8f\x98\x20";
/* Chinese (T) : "淡變 " */
static const char PROTOOLS_CLIP_NAME_FADE_ZH_TW[] = "\xe6\xb7\xa1\xe8\xae\x8a\x20";
/* English : "Sample accurate edit" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_EN[] = "\x53\x61\x6d\x70\x6c\x65\x20\x61\x63\x63\x75\x72\x61\x74\x65\x20\x65\x64\x69\x74";
/* German : "Samplegenaue Bearbeitung" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_DE[] = "\x53\x61\x6d\x70\x6c\x65\x67\x65\x6e\x61\x75\x65\x20\x42\x65\x61\x72\x62\x65\x69\x74\x75\x6e\x67";
/* Spanish : "Edición con precisión de muestra" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_ES[] = "\x45\x64\x69\x63\x69\xc3\xb3\x6e\x20\x63\x6f\x6e\x20\x70\x72\x65\x63\x69\x73\x69\xc3\xb3\x6e\x20\x64\x65\x20\x6d\x75\x65\x73\x74\x72\x61";
/* French : "Modification à l'échantillon près" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_FR[] = "\x4d\x6f\x64\x69\x66\x69\x63\x61\x74\x69\x6f\x6e\x20\xc3\xa0\x20\x6c\x27\xc3\xa9\x63\x68\x61\x6e\x74\x69\x6c\x6c\x6f\x6e\x20\x70\x72\xc3\xa8\x73";
/* Japanese : "サンプル精度編集" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_JA[] = "\xe3\x82\xb5\xe3\x83\xb3\xe3\x83\x97\xe3\x83\xab\xe7\xb2\xbe\xe5\xba\xa6\xe7\xb7\xa8\xe9\x9b\x86";
/* Korean : "샘플 단위 정밀 편집" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_KO[] = "\xec\x83\x98\xed\x94\x8c\x20\xeb\x8b\xa8\xec\x9c\x84\x20\xec\xa0\x95\xeb\xb0\x80\x20\xed\x8e\xb8\xec\xa7\x91";
/* Chinese (S) : "精确采样编辑" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_ZH_CN[] = "\xe7\xb2\xbe\xe7\xa1\xae\xe9\x87\x87\xe6\xa0\xb7\xe7\xbc\x96\xe8\xbe\x91";
/* Chinese (T) : "精確取樣編輯" */
static const char PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_ZH_TW[] = "\xe7\xb2\xbe\xe7\xa2\xba\xe5\x8f\x96\xe6\xa8\xa3\xe7\xb7\xa8\xe8\xbc\xaf";
static int
is_rendered_fade (const char* clipName);
static int
is_sample_accurate_edit (const char* clipName);
static int
remove_sampleAccurateEditClip (AAF_Iface* aafi, aafiAudioTrack* audioTrack, aafiTimelineItem* saeItem);
static int
replace_clipFade (AAF_Iface* aafi, aafiAudioTrack* audioTrack, aafiTimelineItem* fadeItem);
int
protools_AAF (struct AAF_Iface* aafi)
{
int probe = 0;
/* NOTE: CompanyName is "Digidesign, Inc." at least since ProTools 10.3.10.613, and still today (2024) */
if (aafi->aafd->Identification.CompanyName && strcmp (aafi->aafd->Identification.CompanyName, "Digidesign, Inc.") == 0) {
probe++;
}
if (aafi->aafd->Identification.ProductName && strcmp (aafi->aafd->Identification.ProductName, "ProTools") == 0) {
probe++;
}
if (probe == 2) {
return 1;
}
return 0;
}
static int
is_rendered_fade (const char* clipName)
{
return (strcmp (clipName, PROTOOLS_CLIP_NAME_FADE_EN) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_FADE_ES) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_FADE_FR) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_FADE_ZH_CN) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_FADE_ZH_TW) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_FADE_KO) == 0);
}
static int
is_sample_accurate_edit (const char* clipName)
{
return (strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_EN) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_DE) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_ES) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_FR) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_JA) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_ZH_CN) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_ZH_TW) == 0) ||
(strcmp (clipName, PROTOOLS_CLIP_NAME_SAMPLE_ACCURATE_EDIT_KO) == 0);
}
static int
remove_sampleAccurateEditClip (AAF_Iface* aafi, aafiAudioTrack* audioTrack, aafiTimelineItem* saeItem)
{
/*
* Note: In this function, we assume we need to expand a clip to remove an
* attached sample accurate edit. TODO: Ensure it's always possible with ProTools
*/
aafiAudioClip* saeClip = saeItem->data;
if (saeItem->prev) {
if (saeItem->prev->type == AAFI_AUDIO_CLIP) {
aafiAudioClip* leftClip = saeItem->prev->data;
if (saeClip->pos == (leftClip->pos + leftClip->len)) {
aafPosition_t essenceLength = aafi_convertUnit (leftClip->essencePointerList->essenceFile->length, leftClip->essencePointerList->essenceFile->samplerateRational, leftClip->track->edit_rate);
if ((essenceLength - leftClip->essence_offset - leftClip->len) >= saeClip->len) {
debug ("Removing SAE \"%s\" : left clip \"%s\" goes from length %" PRIi64 " to %" PRIi64,
saeClip->essencePointerList->essenceFile->unique_name,
leftClip->essencePointerList->essenceFile->unique_name,
leftClip->len,
leftClip->len + saeClip->len);
leftClip->len += saeClip->len;
aafi_removeTimelineItem (aafi, saeItem);
audioTrack->clipCount--;
return 1;
}
// else {
// debug( L"SAE \"%s\" : left clip \"%s\" has not enough right handle : %lu but %lu is required",
// saeClip->essencePointerList->essenceFile->unique_name,
// leftClip->essencePointerList->essenceFile->unique_name,
// (essenceLength - leftClip->essence_offset - leftClip->len),
// saeClip->len );
// }
}
}
}
if (saeItem->next) {
if (saeItem->next->type == AAFI_AUDIO_CLIP) {
aafiAudioClip* rightClip = saeItem->next->data;
if ((saeClip->pos + saeClip->len) == rightClip->pos) {
if (rightClip->essence_offset >= saeClip->len) {
debug ("Removing SAE \"%s\" : right clip \"%s\" goes from length: %" PRIi64 " to %" PRIi64 ", pos: %" PRIi64 " to %" PRIi64 ", source offset: %" PRIi64 " to %" PRIi64,
saeClip->essencePointerList->essenceFile->unique_name,
rightClip->essencePointerList->essenceFile->unique_name,
rightClip->len,
rightClip->len + saeClip->len,
rightClip->pos,
rightClip->pos - saeClip->len,
rightClip->essence_offset,
rightClip->essence_offset - saeClip->len);
rightClip->pos -= saeClip->len;
rightClip->len += saeClip->len;
rightClip->essence_offset -= saeClip->len;
aafi_removeTimelineItem (aafi, saeItem);
audioTrack->clipCount--;
return 1;
}
// else {
// debug( L"SAE \"%s\" : right clip \"%s\" has not enough left handle : %lu but %lu is required",
// saeClip->essencePointerList->essenceFile->unique_name,
// rightClip->essencePointerList->essenceFile->unique_name,
// rightClip->essence_offset,
// saeClip->len );
// }
}
}
}
return 0;
}
static int
replace_clipFade (AAF_Iface* aafi, aafiAudioTrack* audioTrack, aafiTimelineItem* fadeItem)
{
aafiAudioClip* fadeClip = fadeItem->data;
aafiTimelineItem* prevItem1 = fadeItem->prev;
aafiTimelineItem* prevItem2 = (prevItem1 && prevItem1->prev) ? prevItem1->prev : NULL;
aafiTimelineItem* nextItem1 = fadeItem->next;
aafiTimelineItem* nextItem2 = (nextItem1 && nextItem1->next) ? nextItem1->next : NULL;
aafiAudioClip* prevClip = NULL;
aafiAudioClip* nextClip = NULL;
if (prevItem1 && prevItem1->type == AAFI_AUDIO_CLIP) {
prevClip = prevItem1->data;
if (fadeClip->pos == (prevClip->pos + prevClip->len)) {
/* a previous clip is touching this fadeClip on the left */
if (is_sample_accurate_edit (prevClip->essencePointerList->essenceFile->name)) {
remove_sampleAccurateEditClip (aafi, audioTrack, prevItem1);
if (prevItem2 && prevItem2->type == AAFI_AUDIO_CLIP) {
aafiAudioClip* prevClip2 = prevItem2->data;
if (fadeClip->pos == (prevClip2->pos + prevClip2->len)) {
prevClip = prevClip2;
debug ("Got a clip \"%s\" preceding fadeClip \"%s\"",
prevClip->essencePointerList->essenceFile->unique_name,
fadeClip->essencePointerList->essenceFile->unique_name);
} else {
prevClip = NULL;
}
} else {
prevClip = NULL;
}
} else {
debug ("Got a clip \"%s\" preceding fadeClip \"%s\"",
prevClip->essencePointerList->essenceFile->unique_name,
fadeClip->essencePointerList->essenceFile->unique_name);
}
} else {
prevClip = NULL;
}
}
if (nextItem1 && nextItem1->type == AAFI_AUDIO_CLIP) {
nextClip = nextItem1->data;
if ((fadeClip->pos + fadeClip->len) == nextClip->pos) {
/* a following clip is touching this fadeClip on the right */
if (is_sample_accurate_edit (nextClip->essencePointerList->essenceFile->name)) {
remove_sampleAccurateEditClip (aafi, audioTrack, nextItem1);
if (nextItem2 && nextItem2->type == AAFI_AUDIO_CLIP) {
aafiAudioClip* nextClip2 = nextItem2->data;
if ((fadeClip->pos + fadeClip->len) == nextClip2->pos) {
nextClip = nextClip2;
debug ("Got a clip \"%s\" following fadeClip \"%s\"",
nextClip->essencePointerList->essenceFile->unique_name,
fadeClip->essencePointerList->essenceFile->unique_name);
} else {
nextClip = NULL;
}
} else {
nextClip = NULL;
}
} else {
debug ("Got a clip \"%s\" following fadeClip \"%s\"",
nextClip->essencePointerList->essenceFile->unique_name,
fadeClip->essencePointerList->essenceFile->unique_name);
}
} else {
nextClip = NULL;
}
}
if (!prevClip && !nextClip) {
debug ("FadeClip \"%s\" is not surrounding by any touching clip",
fadeClip->essencePointerList->essenceFile->unique_name);
return 0;
}
/*
* Ensures we have enough handle in surrounding clips to expand by fade length
*/
if (prevClip) {
aafPosition_t essenceLength = aafi_convertUnit (prevClip->essencePointerList->essenceFile->length, prevClip->essencePointerList->essenceFile->samplerateRational, prevClip->track->edit_rate);
if ((essenceLength - prevClip->essence_offset - prevClip->len) < fadeClip->len) {
warning ("Previous clip \"%s\" has not enough handle to build a fade in place of \"%s\"",
prevClip->essencePointerList->essenceFile->unique_name,
fadeClip->essencePointerList->essenceFile->unique_name);
return -1;
}
}
if (nextClip) {
if (nextClip->essence_offset < fadeClip->len) {
warning ("Next clip \"%s\" has not enough handle to build a fade in place of \"%s\"",
nextClip->essencePointerList->essenceFile->unique_name,
fadeClip->essencePointerList->essenceFile->unique_name);
return -1;
}
}
debug ("Replacing fadeClip \"%s\" with a %s transition of length %" PRIi64,
fadeClip->essencePointerList->essenceFile->unique_name,
(prevClip && nextClip) ? "X-Fade" : (nextClip) ? "FadeIn"
: "FadeOut",
fadeClip->len);
/*
* changes existing aafiTimelineItem from aafiAudioClip into aafiTransition
*/
aafPosition_t fadeClipLength = fadeClip->len;
fadeItem->type = AAFI_TRANS;
aafi_freeAudioClip (fadeItem->data);
fadeItem->data = calloc (1, sizeof (aafiTransition));
if (!fadeItem->data) {
error ("Out of memory");
aafi_removeTimelineItem (aafi, fadeItem);
audioTrack->clipCount--;
return 1; /* important ! */
}
aafiTransition* trans = fadeItem->data;
trans->len = fadeClipLength;
trans->flags = AAFI_INTERPOL_LINEAR;
trans->time_a = calloc (2, sizeof (aafRational_t));
trans->value_a = calloc (2, sizeof (aafRational_t));
if (!trans->time_a || !trans->value_a) {
error ("Out of memory");
aafi_removeTimelineItem (aafi, fadeItem);
audioTrack->clipCount--;
return 1; /* important ! */
}
if (prevClip && nextClip) {
prevClip->len += fadeClipLength;
nextClip->pos -= fadeClipLength;
nextClip->len += fadeClipLength;
nextClip->essence_offset -= fadeClipLength;
trans->flags |= AAFI_TRANS_XFADE;
trans->cut_pt = fadeClipLength / 2;
trans->value_a[0].numerator = 0;
trans->value_a[0].denominator = 0;
trans->value_a[1].numerator = 1;
trans->value_a[1].denominator = 1;
} else if (prevClip) {
prevClip->len += fadeClipLength;
trans->flags |= AAFI_TRANS_FADE_OUT;
trans->cut_pt = fadeClipLength;
trans->value_a[0].numerator = 1;
trans->value_a[0].denominator = 1;
trans->value_a[1].numerator = 0;
trans->value_a[1].denominator = 0;
} else if (nextClip) {
nextClip->pos -= fadeClipLength;
nextClip->len += fadeClipLength;
nextClip->essence_offset -= fadeClipLength;
trans->flags |= AAFI_TRANS_FADE_IN;
trans->cut_pt = 0;
trans->value_a[0].numerator = 0;
trans->value_a[0].denominator = 0;
trans->value_a[1].numerator = 1;
trans->value_a[1].denominator = 1;
}
audioTrack->clipCount--;
return 1;
}
int
protools_post_processing (AAF_Iface* aafi)
{
aafiAudioTrack* audioTrack = NULL;
AAFI_foreachAudioTrack (aafi, audioTrack)
{
aafiTimelineItem* audioItem = audioTrack->timelineItems;
while (audioItem != NULL) {
aafiTimelineItem* audioItemNext = audioItem->next;
if (audioItem->type != AAFI_AUDIO_CLIP) {
audioItem = audioItemNext;
continue;
}
aafiAudioClip* audioClip = (aafiAudioClip*)audioItem->data;
char* clipName = audioClip->essencePointerList->essenceFile->name;
int previousClipCount = audioTrack->clipCount;
if ((aafi->ctx.options.protools & AAFI_PROTOOLS_OPT_REPLACE_CLIP_FADES) &&
is_rendered_fade (clipName)) {
replace_clipFade (aafi, audioTrack, audioItem);
if (previousClipCount != audioTrack->clipCount) {
audioItem = audioTrack->timelineItems;
continue;
}
} else if ((aafi->ctx.options.protools & AAFI_PROTOOLS_OPT_REMOVE_SAMPLE_ACCURATE_EDIT) &&
is_sample_accurate_edit (clipName)) {
remove_sampleAccurateEditClip (aafi, audioTrack, audioItem);
if (previousClipCount != audioTrack->clipCount) {
audioItem = audioTrack->timelineItems;
continue;
}
}
audioItem = audioItemNext;
}
}
return 0;
}