Instruções: Como fazer upload (avançado)

Introdução

O carregamento confiável de ativos é a função central de toda integração C2C. Este guia apresenta técnicas avançadas e práticas recomendadas para a criação de um sistema de carregamento robusto, resiliente e eficiente, capaz de apresentar bom desempenho mesmo em ambientes desafiadores.

Pré-requisitos

Caso ainda não tenha feito isso, revise o guia Implementação do C2C: Configuração antes de prosseguir. Você precisará do access_token obtido durante o processo de autenticação e autorização. Continuaremos usando o mesmo ativo de teste do guia Carregamentos básicos.

Parâmetros avançados de ativos

Ao criar ativos no Frame.io, você pode usar vários parâmetros avançados para personalizar o comportamento do carregamento. O parâmetro offset é particularmente importante para a integração adequada.

Offset — Tratamento de dispositivos em pausa

É fundamental fornecer um valor preciso para o parâmetro offset. Esse parâmetro especifica quando uma mídia foi criada e garante que seu dispositivo não carregue conteúdo que não deva ser compartilhado. Quando um dispositivo está em pausa no Frame.io, o usuário está indicando que as mídias criadas durante a pausa não devem ser carregadas. Para mais detalhes, consulte nosso guia sobre a funcionalidade de pausa.

Benefícios adicionais do parâmetro offset

O parâmetro offset oferece outra vantagem significativa para a organização de mídias no Frame.io. Ao carregar conteúdo capturado em uma data anterior, por exemplo, quando um usuário seleciona uma foto tirada na semana anterior durante a reprodução, o parâmetro offset garante que essa mídia apareça nas pastas correspondentes à sua data de captura original, em vez da data atual de carregamento. Essa organização cronológica mantém uma linha do tempo lógica na estrutura do projeto do Frame.io. Sem o parâmetro offset, as mídias antigas apareceriam incorretamente agrupadas com o conteúdo de hoje, o que poderia causar confusão para os editores e outros colaboradores. Talvez você queira oferecer aos usuários a opção de escolher nessa questão por meio da sua interface. Se os usuários preferirem organizar todos os carregamentos pela data atual, independentemente de quando a mídia foi capturada, basta omitir o parâmetro offset, já que seu valor padrão é 0 quando não especificado.

Nosso design de API elimina a necessidade de seu dispositivo rastrear o status de pausa. Em vez disso, ao carregar um arquivo, você indica há quantos segundos o arquivo foi criado. Nosso servidor compara isso com os intervalos de pausa e rejeita o carregamento se ele tiver sido criado durante uma pausa.

Para demonstrar esse recurso, coloque seu dispositivo em pausa pelo menu de três pontos na guia Conexões C2C.

Agora tente carregar um recurso:

${
>curl -X POST https://api.frame.io/v2/devices/assets \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "name": "C2C_TEST_CLIP.mp4",
> "filetype": "video/mp4",
> "filesize": 21136250,
> "offset": 0
> }
>__JSON__
>} | python -m json.tool
Especificação do ponto de acesso da API

A documentação para /v2/devices/assets pode ser encontrada aqui.

Você receberá este erro:

1{
2 "code": 409,
3 "errors": [
4 {
5 "code": 409,
6 "detail": "The channel you're uploading from is currently paused.",
7 "status": 409,
8 "title": "Channel Paused"
9 }
10 ],
11 "message": "Channel Paused"
12}

Se você cancelar a pausa do dispositivo e tentar novamente com a mesma solicitação, o ativo será criado.

No entanto, se o ativo foi criado durante a janela de pausa, você precisa definir offset para refletir quando ele foi realmente criado:

${
>curl -X POST https://api.frame.io/v2/devices/assets \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "name": "C2C_TEST_CLIP.mp4",
> "filetype": "video/mp4",
> "filesize": 21136250,
> "offset": 60
> }
>__JSON__
>} | python -m json.tool

Isso informa ao Frame.io que o ativo foi criado há 60 segundos (durante a pausa), o que aciona adequadamente o erro de Canal pausado. Valores precisos do offset são essenciais para impedir o upload de conteúdo sensível contra a vontade do usuário, incluindo propriedade intelectual protegida, gravação sensível ou outro material restrito.

Deslocamento e novas tentativas

Ao tentar novamente uma chamada de criação de ativo que falhou, lembre-se de atualizar o valor offset. Durante períodos prolongados de novas tentativas, um deslocamento estático pode sair da janela de pausa relevante, permitindo potencialmente envios que deveriam ser bloqueados.

Fazer upload para um canal específico

Se o seu dispositivo tiver vários canais, você pode especificar qual deles usar:

${
>curl -X POST https://api.frame.io/v2/devices/assets \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "name": "C2C_TEST_CLIP.mp4",
> "filetype": "video/mp4",
> "filesize": 21136250,
> "offset": -10,
> "channel": 2
> }
>__JSON__
>} | python -m json.tool

Se não for especificado, o canal padrão é 0. A maioria das integrações não precisará alterar esse valor.

Solicitar um número personalizado de blocos

Por padrão, o back-end do Frame.io divide os arquivos em blocos de aproximadamente 25 MB. Para redes com alto congestionamento, você pode preferir blocos menores. Você pode solicitar um número específico de blocos com o parâmetro parts:

${
>curl -X POST https://api.frame.io/v2/devices/assets \
> --header 'Authorization: Bearer [access_token]' \
> --header 'Content-Type: application/json' \
> --header 'x-client-version: 2.0.0' \
> --data-binary @- <<'__JSON__'
> {
> "name": "C2C_TEST_CLIP.mp4",
> "filetype": "video/mp4",
> "filesize": 21136250,
> "offset": 0,
> "parts": 4
> }
>__JSON__
>} | python -m json.tool

A resposta incluirá quatro URLs de upload:

1{
2 ...
3 "upload_urls": [
4 "https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-01-path]",
5 "https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-02-path]",
6 "https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-03-path]",
7 "https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-04-path]"
8 ],
9 ...
10}

O tamanho do bloco será:

Python
1math.ceiling(float(21136250) / float(4))
2# 5284063 bytes

O último bloco será de 5.284.061 bytes (calculado como 21136250 - 5284063 * 3). Ao solicitar contagens personalizadas de blocos, esteja ciente das limitações do upload multiparte do AWS S3:

  • Cada parte deve ter pelo menos 5 MiB (5.242.880 bytes), exceto a parte final
  • Não pode haver mais de 10.000 partes

Se sua solicitação violar essas restrições, você receberá um erro 500: INTERNAL SERVER ERROR:

{
"code": 500,
"errors": [
{
"code": 500,
"detail": "There was a problem with your request",
"status": 500,
"title": "Something went wrong"
}
],
"message": "Something went wrong"
}

Sempre verifique se a sua contagem personalizada de partes está em conformidade com os requisitos do S3.

Fazer upload com eficiência

Os dispositivos C2C geralmente operam em ambientes de rede desafiadores, portanto, a eficiência é crucial. Aqui estão algumas estratégias para maximizar a taxa de transferência.

Reutilização/agrupamento de conexões TCP

Estabelecer conexões criptografadas requer uma sobrecarga significativa de negociação. Para uma operação eficiente, reutilize as conexões TCP ao fazer várias solicitações. A maioria das bibliotecas HTTP fornece uma abstração Client ou Session que mantém conexões persistentes.

O processo de negociação para uma nova conexão HTTPS inclui handshakes criptográficos e validação de certificados. Ao reutilizar conexões, você realiza essa sobrecarga apenas uma vez, em vez de para cada solicitação.

Referência sobre o handshake TCP

Para obter detalhes técnicos sobre os processos de handshake do TLS, consulte a explicação do Cloudflare.

Para demonstrar a reutilização de conexão com curl, primeiro crie um novo ativo no Frame.io conforme descrito no Guia básico de upload.

Em seguida, divida o arquivo em blocos separados para teste:

$head -c 10568125 ~/Downloads/C2C_TEST_CLIP.mp4 > "C2C_TEST_CLIP-Chunk01"
$tail -c 10568125 ~/Downloads/C2C_TEST_CLIP.mp4 > "C2C_TEST_CLIP-Chunk02"

Agora, faça o upload de ambos os blocos por meio de uma única conexão TCP usando o parâmetro --next do curl:

$curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-1-path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk01 \
>--next -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-2-path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk02

Compare isso com conexões separadas:

$curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-1-path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk01 \
>&& curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-2-path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk02
Reutilizar URLs de blocos

É possível fazer upload para o mesmo URL de bloco várias vezes, portanto, fique à vontade para reutilizar URLs entre os exemplos.

Em testes, a reutilização de conexões normalmente melhora o desempenho em 15 a 20% para uploads sequenciais.

Uploads paralelos

Para obter uma taxa de transferência ainda maior, faça upload de vários blocos simultaneamente:

$curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-1-path] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk01 \
>& \
>curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[part-2-path]\
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk02 \
>&

Com largura de banda suficiente, os uploads paralelos são concluídos aproximadamente no mesmo tempo que o upload individual mais lento.

Para um paralelismo ideal, uma boa regra prática é realizar dois uploads simultâneos por núcleo de CPU. Exceder essa proporção pode levar à disputa por recursos e a retornos decrescentes.

Velocidades de upload paralelo

As condições da rede afetam significativamente o desempenho do upload paralelo. Em alguns ambientes, os uploads sequenciais podem apresentar melhor desempenho do que os paralelos. Implementações avançadas podem monitorar a taxa de transferência e ajustar dinamicamente a simultaneidade. Sempre analise o desempenho em seu ambiente de produção real, em vez de confiar em tempos de referência.

Combinar ambas as abordagens

Para obter o máximo de eficiência, combine o pool de conexões com uploads paralelos. Crie vários processos, cada um utilizando o pool de conexões para sua própria sequência de uploads:

$curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset01-chunk01] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk01 \
>--next -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset01-chunk02] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk02 \
>& \
>curl -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset02-chunk01] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk01 \
>--next -X PUT https://frameio-uploads-production.s3-accelerate.amazonaws.com/parts/[asset02-chunk02] \
> --include \
> --header 'content-type: video/mp4' \
> --header 'x-amz-acl: private' \
> --data-binary @C2C_TEST_CLIP-Chunk02 \
>&
Recursos da biblioteca HTTP

A maioria das bibliotecas HTTP oferece abstrações para o pool de conexões e solicitações paralelas. Experimente as opções da sua biblioteca para determinar a configuração ideal para o seu ambiente.

Acompanhar o progresso do upload

Sua integração deve fornecer uma indicação básica do andamento aos usuários. A granularidade no nível dos blocos é aceitável. Para um upload de três blocos, o andamento pode aumentar de 0% → 33% → 66% → 100% à medida que cada bloco é concluído.

Relatórios de andamento mais detalhados dependem dos recursos da sua biblioteca HTTP. Entre em contato com nossa equipe se precisar de orientação sobre como implementar um acompanhamento de andamento mais detalhado.

Fazer upload de forma confiável

Para tratamento robusto de erros, consulte nosso guia de erros. As seções a seguir pressupõem que você já tenha implementado as estratégias de tratamento de erros descritas nesse guia.

A criação de um carregador com qualidade de produção requer considerações adicionais, além do tratamento de erros em solicitações individuais.

Criar uma fila de upload

Em cenários reais, seu dispositivo pode gerar arquivos de mídia mais rapidamente do que consegue fazer o upload, ou pode sofrer interrupções prolongadas na conexão. A implementação de um sistema de filas separa a criação de mídia do gerenciamento do upload.

Considere uma arquitetura com duas filas:

  1. Uma fila de mídia para registrar arquivos locais no Frame.io
  2. Uma fila de blocos para fazer o upload de blocos individuais de arquivos

Veja aqui uma implementação simplificada:

Python
1# Where we are going to queue new files.
2FILE_QUEUE = Queue()
3
4# Where we are going to queue new chunks.
5CHUNK_QUEUE = Queue()
6
7# The http session that will handle TCP
8# connection pooling for us.
9HTTP_SESSION = http.Session()
10
11def take_picture():
12 """Snaps a picture for the user."""
13
14 image = MY_DEVICE.capture()
15 file_path = MY_DEVICE.write_image(image)
16 FILE_QUEUE.add(file_path)
17
18def task_register_assets():
19 """
20 Pulls snapped pictures from the FILE_QUEUE, registers
21 with Frame.io, and adds the chunks to the CHUNK_QUEUE.
22 """
23 while True:
24 # Get the latest file added to the queue and register
25 # a C2C Asset for it.
26 new_file = FILE_QUEUE.get()
27 asset = c2c.crete_asset_for_file(HTTP_SESSION, new_file)
28
29 # Calculate the size for each chunk
30 chunk_size = c2c.calculate_chunk_size(asset, new_file)
31
32 # Create a message for each chunk with it's parameters
33 # and add it to the
34 # queue.
35 chunk_start = 0
36 for chunk_url in asset.upload_urls:
37 message = {
38 "file_path": new_file,
39 "chunk_url": chunk_url,
40 "chunk_start": chunk_start,
41 "chunk_size": chunk_size,
42 }
43
44 # Put the message in the queue and
45 CHUNK_QUEUE.put(message)
46 chunk_start += chunk_size
47
48def task_upload_chunk():
49 """Takes a chunks and uploads them."""
50
51 while True:
52 info = CHUNK_QUEUE.get()
53 c2c.upload_chunk(HTTP_SESSION, info)
54
55def launch_upload_tasks():
56 """Lauches our Frame.io upload tasks."""
57 # Create a list to hold all of our tasks.
58 tasks = list()
59
60 # Create one task for registering assets.
61 asset_task = run_task_in_thread(task_register_assets)
62 tasks.append(asset_task)
63
64 # Create 2 tasks per CPU core for uploading chunks.
65 for _ in range(0, GET_CPU_COUNT() * 2):
66 chunk_task = run_task_in_thread(task_upload_chunk)
67 tasks.append(chunk_task)
68
69 # Run these tasks until shutdown
70 run_forever(tasks)
Tratamento de erros

No exemplo acima, partimos do princípio de que as funções invocadas para chamadas c2c estão tratando os erros conforme discutido no guia de erros.

Fila persistente entre ciclos de energia

A abordagem de fila na memória funciona bem enquanto o dispositivo permanece ligado, mas o que acontece se houver perda de energia antes que os uploads sejam concluídos? Para criar uma integração verdadeiramente resiliente, precisamos garantir que o dispositivo possa retomar de onde parou após a reinicialização.

Isso requer a persistência do estado da fila no armazenamento entre os ciclos de energia. Um banco de dados incorporado, como o SQLite, oferece uma excelente base para essa funcionalidade.

Sua implementação de fila persistente deve oferecer suporte a estas operações-chave:

  • Adicionar arquivos recém-criados à fila de upload
  • Rastrear quando os ativos são criados com sucesso no Frame.io
  • Registrar quando a criação de ativos falha devido a erros
  • Armazenar informações sobre os blocos de arquivo para tarefas de upload
  • Recuperar o próximo bloco a ser enviado
  • Marcar os blocos como enviados com sucesso
  • Registrar falhas no upload de blocos
  • Fornecer informações sobre o status do arquivo para exibição ao usuário

Veja como poderíamos adaptar nosso exemplo anterior para usar um sistema de armazenamento persistente:

Python
1# Our persistence layer for queuing uploads, potentially using SQLite
2# or another embedded database
3C2C_UPLOAD_STORE = NewC2CUploadStore()
4
5# HTTP session for connection pooling
6HTTP_SESSION = http.Session()
7
8def take_picture():
9 """Captures an image and adds it to the upload queue."""
10 image = MY_DEVICE.capture()
11 file_path = MY_DEVICE.write_image(image)
12
13 # Register the file with our persistent store
14 C2C_UPLOAD_STORE.add_file(file_path)
15
16def task_register_assets():
17 """
18 Processes files from persistent storage and registers
19 them with Frame.io for upload.
20 """
21 while True:
22 # Get the next available file from our store
23 file_record = C2C_UPLOAD_STORE.get_file()
24
25 try:
26 # Register the asset with Frame.io
27 asset = c2c.create_asset_for_file(HTTP_SESSION, file_record)
28 chunk_size = c2c.calculate_chunk_size(asset, file_record)
29
30 # Create entries for each chunk in our persistent store
31 chunk_start = 0
32 for chunk_url in asset.upload_urls:
33 message = {
34 "file_path": file_record,
35 "chunk_url": chunk_url,
36 "chunk_start": chunk_start,
37 "chunk_size": chunk_size,
38 }
39
40 C2C_UPLOAD_STORE.new_chunk(message)
41 chunk_start += chunk_size
42
43 except BaseException as error:
44 # Record the error in our persistent store
45 C2C_UPLOAD_STORE.file_asset_create_error(file_record, error)
46 else:
47 # Mark the asset as successfully created
48 C2C_UPLOAD_STORE.file_asset_created(file_record)
49
50def task_upload_chunk():
51 """Uploads individual file chunks from the persistent queue."""
52 while True:
53 # Get the next chunk, marking it as "in progress" to prevent
54 # other tasks from processing it simultaneously
55 chunk_record = C2C_UPLOAD_STORE.get_chunk()
56
57 try:
58 c2c.upload_chunk(HTTP_SESSION, chunk_record)
59 except BaseException as error:
60 # Record the error for potential retry
61 C2C_UPLOAD_STORE.chunk_error(chunk_record, error)
62 else:
63 # Mark successful completion
64 C2C_UPLOAD_STORE.chunk_success(chunk_record)
65
66def launch_upload_tasks():
67 """Launches Frame.io upload processing tasks."""
68 tasks = []
69
70 # Asset registration task
71 asset_task = run_task_in_thread(task_register_assets)
72 tasks.append(asset_task)
73
74 # Multiple parallel chunk upload tasks
75 worker_count = GET_CPU_COUNT() * 2
76 for _ in range(worker_count):
77 chunk_task = run_task_in_thread(task_upload_chunk)
78 tasks.append(chunk_task)
79
80 # Run indefinitely
81 run_forever(tasks)

Com essa abordagem de armazenamento persistente, sua integração se torna resiliente a interrupções de energia. Quando o dispositivo reinicia, ele simplesmente continua o processamento a partir do último estado salvo. Essa arquitetura também fornece a base para a implementação de recursos mais avançados, como rastreamento de erros e detecção de uploads paralisados.

Rastrear erros de upload

Um sistema de upload robusto deve rastrear cuidadosamente os erros. Após tentar novamente uma operação usando as estratégias do guia de erros, registre essas falhas em seu armazenamento persistente. Isso permite que seu sistema:

  1. Retire a prioridade de uploads problemáticos para evitar que bloqueiem toda a fila
  2. Fornecer informações precisas sobre o status aos usuários
  3. Permitir a intervenção administrativa para problemas persistentes

Quando ocorrer um erro fatal, marque o item para evitar tentativas desnecessárias de repetição.

Gerenciar uploads paralisados

Implemente proteções contra uploads paralisados indefinidamente. Defina uma duração máxima (por exemplo, 30 minutos) após a qual uma tarefa de upload por blocos deve ser encerrada e reiniciada. Isso evita situações em que todos os trabalhadores de upload fiquem bloqueados por operações que não respondem.

Recuperar de falhas silenciosas

Falhas no sistema, queda de energia ou encerramento de processos podem impedir o relatório normal de erros. Ao recuperar itens da sua fila, registre o horário de check-out. Se um item permanecer no estado “em andamento” além de um limite razoável (por exemplo, 30 minutos) sem relatar sucesso ou falha, retorne-o automaticamente ao pool de itens disponíveis para processamento por outro trabalhador.

Mitigar uploads contaminados

Um item “contaminado” da fila falha consistentemente devido a problemas inerentes aos dados ou ao ambiente. Se esses itens voltarem continuamente à fila, eles podem efetivamente bloquear todo o seu sistema de upload. Considere estas estratégias para lidar com tais casos:

  • Após várias falhas, reduza a prioridade do item para que conteúdos mais recentes possam prosseguir
  • Acompanhe tanto os erros explícitos quanto o número de tentativas de processamento
  • Siga as práticas recomendadas de conexão e autorização para distinguir entre problemas ambientais transitórios e problemas intrínsecos do arquivo
  • Implemente limites crescentes de tentativas (por exemplo, tente operações individuais 10 vezes em cada uma das 3 tentativas de tarefa, totalizando 30 tentativas)
  • Forneça uma interface de usuário para reiniciar manualmente uploads problemáticos assim que os problemas ambientais forem resolvidos

Uploads corrompidos podem resultar de:

  • Dados de arquivo corrompidos causando erros de E/S
  • Falhas catastróficas no processo que impedem o relato de erros
  • Erros normalmente recuperáveis, desencadeados por condições subjacentes permanentes

Repetir a tentativa após a reinicialização do sistema

Antes de abandonar definitivamente os uploads problemáticos, marque-os para uma última tentativa após a próxima reinicialização do sistema. Isso resolve casos em que os uploads falham devido a problemas temporários no estado do sistema relacionados à memória, drivers ou alocação de recursos. Se um upload continuar falhando após uma reinicialização completa, você poderá marcá-lo com mais segurança como permanentemente problemático.

Limpar sua fila

Lembre-se de remover arquivos indisponíveis da sua fila. Quando a mídia for removida fisicamente ou os arquivos forem excluídos, elimine as entradas correspondentes da sua fila de upload para evitar erros desnecessários.

É importante ressaltar que você deve limpar sua fila de upload ao se conectar a um novo projeto. A mídia na fila de um projeto nunca deve aparecer em outro. Quando um usuário emparelhar o dispositivo com um projeto diferente, verifique se o projeto foi alterado e, em caso afirmativo, limpe completamente a fila existente.

Próximas etapas

Recomendamos que você entre em contato com nossa equipe caso tenha alguma dúvida e consulte o guia avançado de uploads. Esperamos poder apoiar o andamento da sua integração.