Recursos Avançados
Recursos avançados e capacidades do Qwen Image Edit
Processamento Assíncrono
Webhooks para Processamento em Background
import express from 'express';
import { QwenImageEdit } from 'qwen-image-edit';
const app = express();
app.use(express.json());
const editor = new QwenImageEdit({
apiKey: process.env.QWEN_API_KEY,
webhookUrl: 'https://meuapp.com/webhook/qwen',
webhookSecret: process.env.WEBHOOK_SECRET
});
// ✅ Iniciar processamento assíncrono
app.post('/edit-async', async (req, res) => {
try {
const { imagem, prompt, callbackUrl } = req.body;
const job = await editor.editTextAsync({
imagem,
prompt,
metadata: {
userId: req.user.id,
callbackUrl
}
});
res.json({
jobId: job.id,
status: 'processing',
estimatedTime: job.estimatedTime
});
} catch (error) {
res.status(400).json({ error: error.message });
}
});
// ✅ Receber resultado via webhook
app.post('/webhook/qwen', async (req, res) => {
try {
// Verificar assinatura do webhook
const signature = req.headers['x-qwen-signature'];
const isValid = editor.verifyWebhookSignature(req.body, signature);
if (!isValid) {
return res.status(401).json({ error: 'Assinatura inválida' });
}
const { jobId, status, resultado, metadata } = req.body;
if (status === 'completed') {
// Processar resultado
await processarResultado(jobId, resultado, metadata);
// Notificar usuário se callback URL fornecida
if (metadata.callbackUrl) {
await notificarUsuario(metadata.callbackUrl, {
jobId,
status: 'completed',
imageUrl: resultado.imageUrl
});
}
} else if (status === 'failed') {
console.error(`Job ${jobId} falhou:`, req.body.error);
}
res.json({ received: true });
} catch (error) {
console.error('Erro no webhook:', error);
res.status(500).json({ error: 'Erro interno' });
}
});
async function processarResultado(jobId, resultado, metadata) {
// Salvar resultado no banco de dados
await database.jobs.update(jobId, {
status: 'completed',
resultUrl: resultado.imageUrl,
creditsUsed: resultado.creditos,
completedAt: new Date()
});
console.log(`Job ${jobId} concluído para usuário ${metadata.userId}`);
}
async function notificarUsuario(callbackUrl, dados) {
try {
await fetch(callbackUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(dados)
});
} catch (error) {
console.error('Erro ao notificar usuário:', error);
}
}
Rastreamento de Jobs
class JobTracker {
constructor(editor) {
this.editor = editor;
this.jobs = new Map();
this.callbacks = new Map();
}
async criarJob(opcoes, callback) {
const job = await this.editor.editTextAsync(opcoes);
this.jobs.set(job.id, {
id: job.id,
status: 'processing',
createdAt: new Date(),
opcoes
});
if (callback) {
this.callbacks.set(job.id, callback);
}
// Iniciar polling para verificar status
this.iniciarPolling(job.id);
return job;
}
async iniciarPolling(jobId) {
const intervalo = setInterval(async () => {
try {
const status = await this.editor.getJobStatus(jobId);
this.jobs.set(jobId, {
...this.jobs.get(jobId),
status: status.status,
progress: status.progress
});
if (status.status === 'completed' || status.status === 'failed') {
clearInterval(intervalo);
const callback = this.callbacks.get(jobId);
if (callback) {
callback(status);
this.callbacks.delete(jobId);
}
}
} catch (error) {
console.error(`Erro ao verificar job ${jobId}:`, error);
clearInterval(intervalo);
}
}, 5000); // Verificar a cada 5 segundos
}
obterStatus(jobId) {
return this.jobs.get(jobId);
}
listarJobs(status = null) {
const jobs = Array.from(this.jobs.values());
return status ? jobs.filter(job => job.status === status) : jobs;
}
}
const jobTracker = new JobTracker(editor);
// Usar o tracker
const job = await jobTracker.criarJob({
imagem: './produto.jpg',
prompt: 'Alterar cor do produto para azul'
}, (resultado) => {
console.log('Job concluído:', resultado);
});
console.log('Job criado:', job.id);
Processamento em Lote Otimizado
Controle de Concorrência
import pLimit from 'p-limit';
import pRetry from 'p-retry';
class BatchProcessor {
constructor(editor, opcoes = {}) {
this.editor = editor;
this.concorrencia = opcoes.concorrencia || 3;
this.tentativas = opcoes.tentativas || 3;
this.delayRetry = opcoes.delayRetry || 1000;
this.limit = pLimit(this.concorrencia);
this.estatisticas = {
total: 0,
processados: 0,
sucessos: 0,
falhas: 0,
inicioProcessamento: null
};
}
async processarLote(itens, callback = null) {
this.estatisticas.total = itens.length;
this.estatisticas.inicioProcessamento = new Date();
console.log(`🚀 Iniciando processamento de ${itens.length} itens com concorrência ${this.concorrencia}`);
const resultados = await Promise.allSettled(
itens.map((item, index) =>
this.limit(() => this.processarItem(item, index, callback))
)
);
// Compilar estatísticas finais
const sucessos = resultados.filter(r => r.status === 'fulfilled');
const falhas = resultados.filter(r => r.status === 'rejected');
this.estatisticas.sucessos = sucessos.length;
this.estatisticas.falhas = falhas.length;
const duracao = Date.now() - this.estatisticas.inicioProcessamento.getTime();
console.log(`✅ Processamento concluído em ${duracao}ms`);
console.log(`📊 Sucessos: ${sucessos.length}, Falhas: ${falhas.length}`);
return {
sucessos: sucessos.map(r => r.value),
falhas: falhas.map(r => ({ error: r.reason, item: r.item })),
estatisticas: this.estatisticas
};
}
async processarItem(item, index, callback) {
return pRetry(async () => {
try {
console.log(`📝 Processando item ${index + 1}/${this.estatisticas.total}`);
const resultado = await this.editor.editText({
imagem: item.imagem,
prompt: item.prompt
});
this.estatisticas.processados++;
if (callback) {
callback({
index,
item,
resultado,
status: 'success'
});
}
return { item, resultado, index };
} catch (error) {
console.error(`❌ Erro no item ${index + 1}:`, error.message);
if (callback) {
callback({
index,
item,
error,
status: 'error'
});
}
throw error;
}
}, {
retries: this.tentativas,
minTimeout: this.delayRetry,
onFailedAttempt: (error) => {
console.log(`🔄 Tentativa ${error.attemptNumber} falhou para item ${index + 1}. Tentando novamente...`);
}
});
}
obterProgresso() {
return {
total: this.estatisticas.total,
processados: this.estatisticas.processados,
porcentagem: this.estatisticas.total > 0 ?
(this.estatisticas.processados / this.estatisticas.total * 100).toFixed(1) : 0
};
}
}
// Exemplo de uso
const processor = new BatchProcessor(editor, {
concorrencia: 5,
tentativas: 3,
delayRetry: 2000
});
const itens = [
{ imagem: './img1.jpg', prompt: 'Adicionar logo da empresa' },
{ imagem: './img2.jpg', prompt: 'Alterar cor de fundo para branco' },
{ imagem: './img3.jpg', prompt: 'Remover marca d\'água' }
];
const resultado = await processor.processarLote(itens, (progresso) => {
console.log(`Progresso: ${progresso.status} - Item ${progresso.index + 1}`);
});
console.log('Resultado final:', resultado);
Processamento Paralelo Inteligente
Balanceamento de Carga
import pLimit from 'p-limit';
import pRetry from 'p-retry';
class IntelligentProcessor {
constructor(editores, opcoes = {}) {
this.editores = editores; // Array de instâncias QwenImageEdit
this.opcoes = {
concorrenciaPorEditor: opcoes.concorrenciaPorEditor || 2,
tentativas: opcoes.tentativas || 3,
timeoutRequest: opcoes.timeoutRequest || 60000,
...opcoes
};
// Criar limitadores para cada editor
this.limitadores = this.editores.map(editor => ({
editor,
limit: pLimit(this.opcoes.concorrenciaPorEditor),
estatisticas: {
requisicoes: 0,
sucessos: 0,
falhas: 0,
tempoMedio: 0
}
}));
this.filaProcessamento = [];
this.processandoFila = false;
}
async editText(opcoes) {
return new Promise((resolve, reject) => {
this.filaProcessamento.push({
opcoes,
resolve,
reject,
timestamp: Date.now()
});
this.processarFila();
});
}
async processarFila() {
if (this.processandoFila || this.filaProcessamento.length === 0) {
return;
}
this.processandoFila = true;
while (this.filaProcessamento.length > 0) {
const item = this.filaProcessamento.shift();
// Selecionar melhor editor baseado em estatísticas
const melhorEditor = this.selecionarMelhorEditor();
// Processar com o editor selecionado
melhorEditor.limit(async () => {
try {
const startTime = Date.now();
const resultado = await pRetry(async () => {
return melhorEditor.editor.editText(item.opcoes);
}, {
retries: this.opcoes.tentativas,
minTimeout: 1000
});
const duracao = Date.now() - startTime;
// Atualizar estatísticas
melhorEditor.estatisticas.requisicoes++;
melhorEditor.estatisticas.sucessos++;
melhorEditor.estatisticas.tempoMedio =
(melhorEditor.estatisticas.tempoMedio + duracao) / 2;
item.resolve(resultado);
} catch (error) {
melhorEditor.estatisticas.requisicoes++;
melhorEditor.estatisticas.falhas++;
item.reject(error);
}
});
}
this.processandoFila = false;
}
selecionarMelhorEditor() {
// Calcular score para cada editor
const scores = this.limitadores.map(limitador => {
const stats = limitador.estatisticas;
// Fatores: taxa de sucesso, tempo médio, carga atual
const taxaSucesso = stats.requisicoes > 0 ?
stats.sucessos / stats.requisicoes : 1;
const fatorTempo = stats.tempoMedio > 0 ?
1 / (stats.tempoMedio / 1000) : 1;
const cargaAtual = limitador.limit.activeCount / this.opcoes.concorrenciaPorEditor;
const fatorCarga = 1 - cargaAtual;
return {
limitador,
score: taxaSucesso * fatorTempo * fatorCarga
};
});
// Retornar editor com melhor score
return scores.reduce((melhor, atual) =>
atual.score > melhor.score ? atual : melhor
).limitador;
}
obterEstatisticas() {
return this.limitadores.map((limitador, index) => ({
editorIndex: index,
...limitador.estatisticas,
cargaAtual: limitador.limit.activeCount,
filaAtual: limitador.limit.pendingCount
}));
}
}
// Configurar múltiplos editores
const editores = [
new QwenImageEdit({ apiKey: process.env.QWEN_API_KEY_1, region: 'us-east-1' }),
new QwenImageEdit({ apiKey: process.env.QWEN_API_KEY_2, region: 'us-west-1' }),
new QwenImageEdit({ apiKey: process.env.QWEN_API_KEY_3, region: 'eu-west-1' })
];
const processor = new IntelligentProcessor(editores, {
concorrenciaPorEditor: 3,
tentativas: 2
});
// Usar como um editor normal
const resultado = await processor.editText({
imagem: './imagem.jpg',
prompt: 'Editar texto'
});
Análise Profunda de Imagens
Detecção Avançada de Conteúdo
class AnaliseAvancada {
constructor(editor) {
this.editor = editor;
}
async analisarCompleta(imagem) {
const analises = await Promise.all([
this.analisarTexto(imagem),
this.analisarObjetos(imagem),
this.analisarFaces(imagem),
this.analisarCores(imagem),
this.analisarEstilo(imagem),
this.analisarQualidade(imagem)
]);
return {
texto: analises[0],
objetos: analises[1],
faces: analises[2],
cores: analises[3],
estilo: analises[4],
qualidade: analises[5],
resumo: this.gerarResumo(analises)
};
}
async analisarTexto(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['texto'],
opcoes: {
idiomas: ['pt', 'en', 'es'],
incluirConfianca: true,
incluirPosicao: true
}
});
return {
textos: resultado.texto,
estatisticas: {
totalTextos: resultado.texto.length,
idiomasPredominantes: this.analisarIdiomas(resultado.texto),
confiancaMedia: this.calcularConfiancaMedia(resultado.texto)
}
};
}
async analisarObjetos(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['objetos'],
opcoes: {
incluirConfianca: true,
incluirBoundingBox: true,
categorias: ['pessoas', 'veiculos', 'animais', 'objetos']
}
});
return {
objetos: resultado.objetos,
estatisticas: {
totalObjetos: resultado.objetos.length,
categorias: this.agruparPorCategoria(resultado.objetos),
densidadeObjetos: this.calcularDensidade(resultado.objetos)
}
};
}
async analisarFaces(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['faces'],
opcoes: {
incluirEmocoes: true,
incluirIdade: true,
incluirGenero: true,
incluirPosicao: true
}
});
return {
faces: resultado.faces,
estatisticas: {
totalFaces: resultado.faces.length,
emocoesPredominantes: this.analisarEmocoes(resultado.faces),
faixaEtaria: this.analisarIdades(resultado.faces)
}
};
}
async analisarCores(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['cores'],
opcoes: {
numeroCores: 10,
incluirPorcentagem: true,
incluirHSL: true,
incluirRGB: true
}
});
return {
cores: resultado.cores,
paleta: this.gerarPaleta(resultado.cores),
estatisticas: {
corPredominante: resultado.cores[0],
saturacaoMedia: this.calcularSaturacaoMedia(resultado.cores),
brilhoMedio: this.calcularBrilhoMedio(resultado.cores)
}
};
}
async analisarEstilo(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['estilo'],
opcoes: {
categorias: ['fotografia', 'ilustracao', 'arte', 'design'],
incluirConfianca: true
}
});
return {
estilo: resultado.estilo,
categoria: resultado.estilo.categoria,
confianca: resultado.estilo.confianca,
caracteristicas: resultado.estilo.caracteristicas
};
}
async analisarQualidade(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['qualidade'],
opcoes: {
metricas: ['nitidez', 'ruido', 'exposicao', 'contraste'],
incluirSugestoes: true
}
});
return {
qualidade: resultado.qualidade,
pontuacao: resultado.qualidade.pontuacaoGeral,
problemas: resultado.qualidade.problemas,
sugestoes: resultado.qualidade.sugestoes
};
}
gerarResumo(analises) {
const [texto, objetos, faces, cores, estilo, qualidade] = analises;
return {
tipo: this.determinarTipoImagem(analises),
complexidade: this.calcularComplexidade(analises),
adequacaoEdicao: this.avaliarAdequacaoEdicao(analises),
sugestoesOtimizacao: this.gerarSugestoesOtimizacao(analises)
};
}
determinarTipoImagem(analises) {
const [texto, objetos, faces] = analises;
if (faces.faces.length > 0) return 'retrato';
if (texto.textos.length > 5) return 'documento';
if (objetos.objetos.length > 10) return 'cena_complexa';
return 'geral';
}
calcularComplexidade(analises) {
const [texto, objetos, faces, cores] = analises;
let score = 0;
score += texto.textos.length * 2;
score += objetos.objetos.length * 1;
score += faces.faces.length * 3;
score += cores.cores.length * 0.5;
if (score < 10) return 'baixa';
if (score < 25) return 'media';
return 'alta';
}
avaliarAdequacaoEdicao(analises) {
const [, , , , , qualidade] = analises;
if (qualidade.pontuacao > 0.8) return 'excelente';
if (qualidade.pontuacao > 0.6) return 'boa';
if (qualidade.pontuacao > 0.4) return 'regular';
return 'ruim';
}
gerarSugestoesOtimizacao(analises) {
const sugestoes = [];
const [, , , , , qualidade] = analises;
if (qualidade.qualidade.nitidez < 0.5) {
sugestoes.push('Considere aplicar filtro de nitidez antes da edição');
}
if (qualidade.qualidade.ruido > 0.7) {
sugestoes.push('Recomenda-se redução de ruído para melhor resultado');
}
if (qualidade.qualidade.exposicao < 0.3) {
sugestoes.push('Imagem subexposta - considere ajustar brilho');
}
return sugestoes;
}
}
const analisador = new AnaliseAvancada(editor);
// Análise completa
const analise = await analisador.analisarCompleta('./imagem-complexa.jpg');
console.log('Análise completa:', JSON.stringify(analise, null, 2));
Detecção de Conteúdo Sensível
Sistema de Moderação
class ModeracaoConteudo {
constructor(editor) {
this.editor = editor;
this.politicas = {
violencia: { ativo: true, limite: 0.3 },
nudez: { ativo: true, limite: 0.2 },
drogas: { ativo: true, limite: 0.4 },
linguagemOfensiva: { ativo: true, limite: 0.3 }
};
}
async verificarConteudo(imagem) {
const resultado = await this.editor.analyzeImage({
imagem,
detectar: ['conteudo_sensivel'],
opcoes: {
categorias: Object.keys(this.politicas),
incluirConfianca: true,
incluirDetalhes: true
}
});
const violacoes = this.identificarViolacoes(resultado.conteudoSensivel);
return {
aprovado: violacoes.length === 0,
violacoes,
pontuacoes: resultado.conteudoSensivel,
recomendacoes: this.gerarRecomendacoes(violacoes)
};
}
identificarViolacoes(pontuacoes) {
const violacoes = [];
for (const [categoria, pontuacao] of Object.entries(pontuacoes)) {
const politica = this.politicas[categoria];
if (politica && politica.ativo && pontuacao.confianca > politica.limite) {
violacoes.push({
categoria,
pontuacao: pontuacao.confianca,
limite: politica.limite,
detalhes: pontuacao.detalhes
});
}
}
return violacoes;
}
gerarRecomendacoes(violacoes) {
const recomendacoes = [];
for (const violacao of violacoes) {
switch (violacao.categoria) {
case 'violencia':
recomendacoes.push('Remover ou censurar conteúdo violento');
break;
case 'nudez':
recomendacoes.push('Aplicar censura ou remover conteúdo explícito');
break;
case 'drogas':
recomendacoes.push('Remover referências a substâncias ilegais');
break;
case 'linguagemOfensiva':
recomendacoes.push('Censurar ou substituir linguagem ofensiva');
break;
}
}
return recomendacoes;
}
async editarComModeracao(opcoes) {
// Verificar conteúdo antes da edição
const moderacao = await this.verificarConteudo(opcoes.imagem);
if (!moderacao.aprovado) {
throw new Error(`Conteúdo rejeitado: ${moderacao.violacoes.map(v => v.categoria).join(', ')}`);
}
// Proceder com edição
const resultado = await this.editor.editText(opcoes);
// Verificar resultado após edição
const moderacaoResultado = await this.verificarConteudo(resultado.imagem);
if (!moderacaoResultado.aprovado) {
console.warn('Resultado da edição contém conteúdo sensível:', moderacaoResultado.violacoes);
}
return {
...resultado,
moderacao: {
original: moderacao,
resultado: moderacaoResultado
}
};
}
}
const moderador = new ModeracaoConteudo(editor);
// Editar com moderação
try {
const resultado = await moderador.editarComModeracao({
imagem: './imagem.jpg',
prompt: 'Adicionar texto motivacional'
});
console.log('Edição aprovada:', resultado);
} catch (error) {
console.error('Edição rejeitada:', error.message);
}
Edição Condicional
Edição Baseada em Análise
class EdicaoCondicional {
constructor(editor) {
this.editor = editor;
this.analisador = new AnaliseAvancada(editor);
}
async editarSeCondicao(imagem, condicoes, edicoes) {
// Analisar imagem primeiro
const analise = await this.analisador.analisarCompleta(imagem);
// Verificar condições
const condicoesAtendidas = this.verificarCondicoes(analise, condicoes);
if (condicoesAtendidas.length === 0) {
return {
editado: false,
motivo: 'Nenhuma condição atendida',
analise
};
}
// Aplicar edições baseadas nas condições atendidas
let imagemAtual = imagem;
const edicoesAplicadas = [];
for (const condicao of condicoesAtendidas) {
const edicao = edicoes[condicao.nome];
if (edicao) {
console.log(`Aplicando edição para condição: ${condicao.nome}`);
const resultado = await this.editor.editText({
imagem: imagemAtual,
prompt: edicao.prompt,
...edicao.opcoes
});
imagemAtual = resultado.imagem;
edicoesAplicadas.push({
condicao: condicao.nome,
edicao: edicao.prompt,
creditos: resultado.creditos
});
}
}
return {
editado: true,
imagemFinal: imagemAtual,
edicoesAplicadas,
analise,
creditosTotal: edicoesAplicadas.reduce((total, e) => total + e.creditos, 0)
};
}
verificarCondicoes(analise, condicoes) {
const atendidas = [];
for (const [nome, condicao] of Object.entries(condicoes)) {
if (this.avaliarCondicao(analise, condicao)) {
atendidas.push({ nome, ...condicao });
}
}
return atendidas;
}
avaliarCondicao(analise, condicao) {
switch (condicao.tipo) {
case 'tem_texto':
return analise.texto.textos.length > 0;
case 'tem_faces':
return analise.faces.faces.length >= (condicao.minimo || 1);
case 'qualidade_baixa':
return analise.qualidade.pontuacao < (condicao.limite || 0.5);
case 'cor_predominante':
const corPredominante = analise.cores.cores[0];
return this.compararCor(corPredominante.rgb, condicao.cor);
case 'tem_objeto':
return analise.objetos.objetos.some(obj =>
obj.nome.toLowerCase().includes(condicao.objeto.toLowerCase())
);
case 'estilo':
return analise.estilo.categoria === condicao.categoria;
case 'complexidade':
return analise.resumo.complexidade === condicao.nivel;
default:
return false;
}
}
compararCor(rgb1, rgb2, tolerancia = 50) {
const distancia = Math.sqrt(
Math.pow(rgb1.r - rgb2.r, 2) +
Math.pow(rgb1.g - rgb2.g, 2) +
Math.pow(rgb1.b - rgb2.b, 2)
);
return distancia <= tolerancia;
}
}
// Exemplo de uso
const edicaoCondicional = new EdicaoCondicional(editor);
const condicoes = {
'melhorar_qualidade': {
tipo: 'qualidade_baixa',
limite: 0.6
},
'adicionar_logo': {
tipo: 'tem_objeto',
objeto: 'produto'
},
'ajustar_cores': {
tipo: 'cor_predominante',
cor: { r: 255, g: 255, b: 255 } // Branco predominante
}
};
const edicoes = {
'melhorar_qualidade': {
prompt: 'Melhorar nitidez e reduzir ruído da imagem',
opcoes: { qualidade: 'alta' }
},
'adicionar_logo': {
prompt: 'Adicionar logo da empresa no canto superior direito',
opcoes: { posicao: 'canto_superior_direito' }
},
'ajustar_cores': {
prompt: 'Ajustar cores para ter mais contraste e vivacidade',
opcoes: { intensidade: 'media' }
}
};
const resultado = await edicaoCondicional.editarSeCondicao(
'./produto.jpg',
condicoes,
edicoes
);
console.log('Resultado da edição condicional:', resultado);
Continue explorando: Estes recursos avançados permitem criar soluções sofisticadas e escaláveis com Qwen Image Edit. Para implementações específicas, consulte nossa documentação da API ou entre em contato com o suporte.