clipkly 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clipkly-0.1.0/LICENSE +21 -0
- clipkly-0.1.0/MANIFEST.in +1 -0
- clipkly-0.1.0/PKG-INFO +210 -0
- clipkly-0.1.0/README.md +183 -0
- clipkly-0.1.0/clipkly/__init__.py +1 -0
- clipkly-0.1.0/clipkly/cli.py +152 -0
- clipkly-0.1.0/clipkly.egg-info/PKG-INFO +210 -0
- clipkly-0.1.0/clipkly.egg-info/SOURCES.txt +13 -0
- clipkly-0.1.0/clipkly.egg-info/dependency_links.txt +1 -0
- clipkly-0.1.0/clipkly.egg-info/entry_points.txt +2 -0
- clipkly-0.1.0/clipkly.egg-info/requires.txt +3 -0
- clipkly-0.1.0/clipkly.egg-info/top_level.txt +1 -0
- clipkly-0.1.0/pyproject.toml +3 -0
- clipkly-0.1.0/setup.cfg +4 -0
- clipkly-0.1.0/setup.py +25 -0
clipkly-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Judlup Luna
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include README.md
|
clipkly-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clipkly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Corta automáticamente clips de video a partir de subtítulos o timecodes definidos en un JSON.
|
|
5
|
+
Home-page: https://github.com/judlup/clipkly
|
|
6
|
+
Author: Julian Dario Luna Patiño
|
|
7
|
+
Author-email: judlup@trycatch.tv
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Requires-Dist: tqdm
|
|
16
|
+
Requires-Dist: openpyxl
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# 🎬 Clipkly
|
|
30
|
+
|
|
31
|
+
`Clipkly` es una herramienta en Python para cortar automáticamente clips desde dos versiones de un mismo video (horizontal y vertical), usando un archivo `.json` con los timecodes de los mejores momentos.
|
|
32
|
+
|
|
33
|
+
Soporta desfase (`--offset`) cuando el video vertical empieza en un punto diferente al horizontal (por ejemplo, por edición o plataformas como TikTok).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Uso rápido
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python clipkly.py --offset 403.025 --vertical video_v.mp4
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Esto generará clips desde:
|
|
44
|
+
|
|
45
|
+
- `video_v.mp4` (con offset aplicado) → en `clips/vertical/`
|
|
46
|
+
|
|
47
|
+
Si también incluyes `--horizontal`, generará clips desde `video_h.mp4` (sin offset) → en `clips/horizontal/`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🧾 Formato del archivo clips.json
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
[
|
|
55
|
+
{
|
|
56
|
+
"start": "01:46:31.760",
|
|
57
|
+
"end": "01:47:17.199",
|
|
58
|
+
"slug": "titulo_del_video",
|
|
59
|
+
"titulo": "titulo del video optimizado para SEO",
|
|
60
|
+
"descripcion": "breve descripción del contenido del clip",
|
|
61
|
+
"feeling": "sentimiento del clip",
|
|
62
|
+
"category" : "categoria del clip"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
🧠 Puedes generar este archivo a partir de los subtítulos de YouTube (ver más abajo).
|
|
68
|
+
|
|
69
|
+
📥 Cómo obtener los subtítulos (`.srt`):
|
|
70
|
+
|
|
71
|
+
1. Ve a https://www.downloadyoutubesubtitles.com/es
|
|
72
|
+
2. Pega el enlace del video de YouTube
|
|
73
|
+
3. Descarga el archivo en formato `.srt` (idealmente en español)
|
|
74
|
+
4. Abre Google AI Studio
|
|
75
|
+
5. Usa este prompt:
|
|
76
|
+
|
|
77
|
+
```plaintext
|
|
78
|
+
Estos son los subtítulos de la transmisión, puede darme la lista de los mejores momentos para sacar los clips, también en formato JSON:
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
"start": "01:46:31.760",
|
|
82
|
+
"end": "01:47:17.199",
|
|
83
|
+
"slug": "titulo_del_video",
|
|
84
|
+
"titulo": "titulo del video optimizado para seo",
|
|
85
|
+
"description": "descripción o sinapsis del video",
|
|
86
|
+
"feeling": "sentimiento del clip",
|
|
87
|
+
"category" : " categoria del clip"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ⚙️ Argumentos del script
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python clipkly.py [opciones]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Opción | Descripción |
|
|
100
|
+
|------------------|-----------------------------------------------------------------------------|
|
|
101
|
+
| `--offset` | Offset en segundos solo para el video vertical (default: 0.0) |
|
|
102
|
+
| `--horizontal` | Ruta al video horizontal (opcional) |
|
|
103
|
+
| `--vertical` | Ruta al video vertical (opcional) |
|
|
104
|
+
| `--json` | Ruta al archivo JSON con los timecodes (default: `clips.json`) |
|
|
105
|
+
| `--filter` | Filtra los clips por categoría (`inspiracional`, `tips`, etc.) |
|
|
106
|
+
| `--duracion` | Filtra clips por duración: `muy_corto`, `ideal`, `largo`, `muy_largo` |
|
|
107
|
+
| `--dry-run` | Muestra los comandos sin ejecutarlos |
|
|
108
|
+
| `--version`, `-v`| Muestra la versión del script |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 📁 Estructura generada
|
|
113
|
+
|
|
114
|
+
```plaintext
|
|
115
|
+
📁 clips/
|
|
116
|
+
├── 📁 horizontal/ ← Clips del video horizontal (sin offset)
|
|
117
|
+
├── 📁 vertical/ ← Clips del video vertical (con offset)
|
|
118
|
+
└── 📄 estado_clips.xlsx ← Archivo Excel con metadatos editoriales
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Este Excel incluye:
|
|
122
|
+
|
|
123
|
+
- duración del clip en segundos (`duracion_segundos`)
|
|
124
|
+
- duración en formato `min:seg` (`duracion_mmss`)
|
|
125
|
+
- clasificación automática:
|
|
126
|
+
- `muy_corto`: hasta 30s
|
|
127
|
+
- `ideal`: entre 31s y 90s
|
|
128
|
+
- `largo`: entre 91s y 179s
|
|
129
|
+
- `muy_largo`: más de 3 minutos
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 📦 Requisitos
|
|
134
|
+
|
|
135
|
+
- Python 3.7+
|
|
136
|
+
- `ffmpeg` instalado y en el PATH
|
|
137
|
+
- Ejecutar: `pip install -r requirements.txt` para instalar dependencias
|
|
138
|
+
|
|
139
|
+
Dependencias mínimas:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
pandas
|
|
143
|
+
tqdm
|
|
144
|
+
openpyxl
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 💡 Funcionalidades
|
|
150
|
+
|
|
151
|
+
- ✂️ Corta clips precisos con FFmpeg sin recodificar (`-c copy`)
|
|
152
|
+
- 🕒 Aplica offset únicamente al video vertical
|
|
153
|
+
- 📁 Genera carpetas separadas para cada versión (`clips/horizontal` y `clips/vertical`)
|
|
154
|
+
- 🧼 Limpia automáticamente los nombres de archivo
|
|
155
|
+
- 🧠 Exporta Excel para planeación editorial (`estado_clips.xlsx`)
|
|
156
|
+
- 🔍 Filtros por duración y categoría
|
|
157
|
+
- 🔄 Modo `--dry-run` para validar sin ejecutar
|
|
158
|
+
- 🧾 Modo CLI personalizable
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🖥️ Alias útil en consola
|
|
163
|
+
|
|
164
|
+
Powershell:
|
|
165
|
+
|
|
166
|
+
```powershell
|
|
167
|
+
Set-Alias clipkly python ./clipkly.py
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Linux/Bash:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
alias clipkly='python ./clipkly.py'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Entonces puedes correr:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
clipkly --offset 3.25 --vertical video_v.mp4
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 📌 Ejemplo completo
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python clipkly.py --offset 403.025 --horizontal video_h.mp4 --vertical video_v.mp4 --json clips.json --duracion ideal
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🔜 Próximas mejoras sugeridas
|
|
193
|
+
|
|
194
|
+
- 🎞️ Subtítulos quemados en los clips
|
|
195
|
+
- 📸 Generación de miniaturas automáticas
|
|
196
|
+
- 🌐 Interfaz gráfica simple con Gradio
|
|
197
|
+
- 📊 Dashboard para gestión de clips y publicaciones
|
|
198
|
+
- ☁️ Integración con plataformas como Notion o Google Sheets
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 🙌 Créditos
|
|
203
|
+
|
|
204
|
+
Este proyecto fue creado por **Julian Dario Luna Patiño**, ingeniero de software, arquitecto de soluciones en la nube y creador de contenido en [TryCatch.tv](https://trycatch.tv).
|
|
205
|
+
|
|
206
|
+
**clipkly** nació como una herramienta práctica para automatizar la creación de clips a partir de transmisiones en vivo, especialmente útil para quienes trabajan con contenido en plataformas como YouTube, TikTok e Instagram.
|
|
207
|
+
|
|
208
|
+
Contacto: [judlup@trycatch.tv](mailto:judlup@trycatch.tv)
|
|
209
|
+
|
|
210
|
+
✨ Dedicado con cariño a **Nikol Daniela** ❤️
|
clipkly-0.1.0/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
|
|
2
|
+
# 🎬 Clipkly
|
|
3
|
+
|
|
4
|
+
`Clipkly` es una herramienta en Python para cortar automáticamente clips desde dos versiones de un mismo video (horizontal y vertical), usando un archivo `.json` con los timecodes de los mejores momentos.
|
|
5
|
+
|
|
6
|
+
Soporta desfase (`--offset`) cuando el video vertical empieza en un punto diferente al horizontal (por ejemplo, por edición o plataformas como TikTok).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 🚀 Uso rápido
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python clipkly.py --offset 403.025 --vertical video_v.mp4
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Esto generará clips desde:
|
|
17
|
+
|
|
18
|
+
- `video_v.mp4` (con offset aplicado) → en `clips/vertical/`
|
|
19
|
+
|
|
20
|
+
Si también incluyes `--horizontal`, generará clips desde `video_h.mp4` (sin offset) → en `clips/horizontal/`.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🧾 Formato del archivo clips.json
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
[
|
|
28
|
+
{
|
|
29
|
+
"start": "01:46:31.760",
|
|
30
|
+
"end": "01:47:17.199",
|
|
31
|
+
"slug": "titulo_del_video",
|
|
32
|
+
"titulo": "titulo del video optimizado para SEO",
|
|
33
|
+
"descripcion": "breve descripción del contenido del clip",
|
|
34
|
+
"feeling": "sentimiento del clip",
|
|
35
|
+
"category" : "categoria del clip"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
🧠 Puedes generar este archivo a partir de los subtítulos de YouTube (ver más abajo).
|
|
41
|
+
|
|
42
|
+
📥 Cómo obtener los subtítulos (`.srt`):
|
|
43
|
+
|
|
44
|
+
1. Ve a https://www.downloadyoutubesubtitles.com/es
|
|
45
|
+
2. Pega el enlace del video de YouTube
|
|
46
|
+
3. Descarga el archivo en formato `.srt` (idealmente en español)
|
|
47
|
+
4. Abre Google AI Studio
|
|
48
|
+
5. Usa este prompt:
|
|
49
|
+
|
|
50
|
+
```plaintext
|
|
51
|
+
Estos son los subtítulos de la transmisión, puede darme la lista de los mejores momentos para sacar los clips, también en formato JSON:
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
"start": "01:46:31.760",
|
|
55
|
+
"end": "01:47:17.199",
|
|
56
|
+
"slug": "titulo_del_video",
|
|
57
|
+
"titulo": "titulo del video optimizado para seo",
|
|
58
|
+
"description": "descripción o sinapsis del video",
|
|
59
|
+
"feeling": "sentimiento del clip",
|
|
60
|
+
"category" : " categoria del clip"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## ⚙️ Argumentos del script
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
python clipkly.py [opciones]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
| Opción | Descripción |
|
|
73
|
+
|------------------|-----------------------------------------------------------------------------|
|
|
74
|
+
| `--offset` | Offset en segundos solo para el video vertical (default: 0.0) |
|
|
75
|
+
| `--horizontal` | Ruta al video horizontal (opcional) |
|
|
76
|
+
| `--vertical` | Ruta al video vertical (opcional) |
|
|
77
|
+
| `--json` | Ruta al archivo JSON con los timecodes (default: `clips.json`) |
|
|
78
|
+
| `--filter` | Filtra los clips por categoría (`inspiracional`, `tips`, etc.) |
|
|
79
|
+
| `--duracion` | Filtra clips por duración: `muy_corto`, `ideal`, `largo`, `muy_largo` |
|
|
80
|
+
| `--dry-run` | Muestra los comandos sin ejecutarlos |
|
|
81
|
+
| `--version`, `-v`| Muestra la versión del script |
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 📁 Estructura generada
|
|
86
|
+
|
|
87
|
+
```plaintext
|
|
88
|
+
📁 clips/
|
|
89
|
+
├── 📁 horizontal/ ← Clips del video horizontal (sin offset)
|
|
90
|
+
├── 📁 vertical/ ← Clips del video vertical (con offset)
|
|
91
|
+
└── 📄 estado_clips.xlsx ← Archivo Excel con metadatos editoriales
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Este Excel incluye:
|
|
95
|
+
|
|
96
|
+
- duración del clip en segundos (`duracion_segundos`)
|
|
97
|
+
- duración en formato `min:seg` (`duracion_mmss`)
|
|
98
|
+
- clasificación automática:
|
|
99
|
+
- `muy_corto`: hasta 30s
|
|
100
|
+
- `ideal`: entre 31s y 90s
|
|
101
|
+
- `largo`: entre 91s y 179s
|
|
102
|
+
- `muy_largo`: más de 3 minutos
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 📦 Requisitos
|
|
107
|
+
|
|
108
|
+
- Python 3.7+
|
|
109
|
+
- `ffmpeg` instalado y en el PATH
|
|
110
|
+
- Ejecutar: `pip install -r requirements.txt` para instalar dependencias
|
|
111
|
+
|
|
112
|
+
Dependencias mínimas:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
pandas
|
|
116
|
+
tqdm
|
|
117
|
+
openpyxl
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 💡 Funcionalidades
|
|
123
|
+
|
|
124
|
+
- ✂️ Corta clips precisos con FFmpeg sin recodificar (`-c copy`)
|
|
125
|
+
- 🕒 Aplica offset únicamente al video vertical
|
|
126
|
+
- 📁 Genera carpetas separadas para cada versión (`clips/horizontal` y `clips/vertical`)
|
|
127
|
+
- 🧼 Limpia automáticamente los nombres de archivo
|
|
128
|
+
- 🧠 Exporta Excel para planeación editorial (`estado_clips.xlsx`)
|
|
129
|
+
- 🔍 Filtros por duración y categoría
|
|
130
|
+
- 🔄 Modo `--dry-run` para validar sin ejecutar
|
|
131
|
+
- 🧾 Modo CLI personalizable
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🖥️ Alias útil en consola
|
|
136
|
+
|
|
137
|
+
Powershell:
|
|
138
|
+
|
|
139
|
+
```powershell
|
|
140
|
+
Set-Alias clipkly python ./clipkly.py
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Linux/Bash:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
alias clipkly='python ./clipkly.py'
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Entonces puedes correr:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
clipkly --offset 3.25 --vertical video_v.mp4
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 📌 Ejemplo completo
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
python clipkly.py --offset 403.025 --horizontal video_h.mp4 --vertical video_v.mp4 --json clips.json --duracion ideal
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 🔜 Próximas mejoras sugeridas
|
|
166
|
+
|
|
167
|
+
- 🎞️ Subtítulos quemados en los clips
|
|
168
|
+
- 📸 Generación de miniaturas automáticas
|
|
169
|
+
- 🌐 Interfaz gráfica simple con Gradio
|
|
170
|
+
- 📊 Dashboard para gestión de clips y publicaciones
|
|
171
|
+
- ☁️ Integración con plataformas como Notion o Google Sheets
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 🙌 Créditos
|
|
176
|
+
|
|
177
|
+
Este proyecto fue creado por **Julian Dario Luna Patiño**, ingeniero de software, arquitecto de soluciones en la nube y creador de contenido en [TryCatch.tv](https://trycatch.tv).
|
|
178
|
+
|
|
179
|
+
**clipkly** nació como una herramienta práctica para automatizar la creación de clips a partir de transmisiones en vivo, especialmente útil para quienes trabajan con contenido en plataformas como YouTube, TikTok e Instagram.
|
|
180
|
+
|
|
181
|
+
Contacto: [judlup@trycatch.tv](mailto:judlup@trycatch.tv)
|
|
182
|
+
|
|
183
|
+
✨ Dedicado con cariño a **Nikol Daniela** ❤️
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# clipkly/__init__.py
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import pathlib
|
|
5
|
+
import re
|
|
6
|
+
import shlex
|
|
7
|
+
import subprocess
|
|
8
|
+
import unicodedata
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from tqdm import tqdm
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
VERSION = "0.1.0"
|
|
17
|
+
|
|
18
|
+
parser = argparse.ArgumentParser(
|
|
19
|
+
description="Recorta clips desde uno o dos videos según un archivo JSON.")
|
|
20
|
+
parser.add_argument("--offset", type=float, default=0.0,
|
|
21
|
+
help="Offset (en segundos) solo para el video vertical")
|
|
22
|
+
parser.add_argument("--horizontal", type=str,
|
|
23
|
+
help="Ruta al archivo de video horizontal (opcional)")
|
|
24
|
+
parser.add_argument("--vertical", type=str,
|
|
25
|
+
help="Ruta al archivo de video vertical (opcional)")
|
|
26
|
+
parser.add_argument("--json", type=str, default="clips.json",
|
|
27
|
+
help="Archivo JSON con la definición de los clips")
|
|
28
|
+
parser.add_argument("--filter", type=str,
|
|
29
|
+
help="Filtrar clips por categoría (opcional)")
|
|
30
|
+
parser.add_argument("--duracion", type=str, choices=[
|
|
31
|
+
"muy_corto", "ideal", "largo", "muy_largo"], help="Filtrar clips por duración clasificada (opcional)")
|
|
32
|
+
parser.add_argument("--dry-run", action="store_true",
|
|
33
|
+
help="Solo mostrar lo que se haría sin ejecutar FFmpeg")
|
|
34
|
+
parser.add_argument("--version", "-v", action="store_true",
|
|
35
|
+
help="Mostrar versión y salir")
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
if args.version:
|
|
39
|
+
print(f"clipkly v{VERSION}")
|
|
40
|
+
exit()
|
|
41
|
+
|
|
42
|
+
OFFSET_SECONDS = args.offset
|
|
43
|
+
VIDEO_H = args.horizontal
|
|
44
|
+
VIDEO_V = args.vertical
|
|
45
|
+
TIMECODES = args.json
|
|
46
|
+
FILTER = args.filter
|
|
47
|
+
DURACION = args.duracion
|
|
48
|
+
DRY_RUN = args.dry_run
|
|
49
|
+
|
|
50
|
+
ROOT_DIR = pathlib.Path("clips")
|
|
51
|
+
OUT_H = ROOT_DIR / "horizontal"
|
|
52
|
+
OUT_V = ROOT_DIR / "vertical"
|
|
53
|
+
OUT_H.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
OUT_V.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
|
|
56
|
+
def slugify(text: str) -> str:
|
|
57
|
+
text = unicodedata.normalize("NFKD", text)
|
|
58
|
+
text = text.encode("ascii", "ignore").decode("ascii")
|
|
59
|
+
text = re.sub(r"\W+", "_", text.lower())
|
|
60
|
+
return re.sub(r"_+", "_", text[:40].strip("_")) or "clip"
|
|
61
|
+
|
|
62
|
+
def hhmmss_to_sec(t: str) -> float:
|
|
63
|
+
dt = datetime.strptime(
|
|
64
|
+
t, "%H:%M:%S.%f") if "." in t else datetime.strptime(t, "%H:%M:%S")
|
|
65
|
+
return dt.hour * 3600 + dt.minute * 60 + dt.second + dt.microsecond / 1e6
|
|
66
|
+
|
|
67
|
+
def sec_to_hhmmss(seconds: float) -> str:
|
|
68
|
+
s = max(0, seconds)
|
|
69
|
+
h = int(s // 3600)
|
|
70
|
+
m = int((s % 3600) // 60)
|
|
71
|
+
s = s % 60
|
|
72
|
+
return f"{h:02}:{m:02}:{s:06.3f}"
|
|
73
|
+
|
|
74
|
+
with open(TIMECODES, encoding="utf-8") as f:
|
|
75
|
+
raw_clips = json.load(f)
|
|
76
|
+
|
|
77
|
+
clips = [c for c in raw_clips if not FILTER or c.get("category") == FILTER]
|
|
78
|
+
|
|
79
|
+
def clasifica(segundos):
|
|
80
|
+
if segundos <= 30:
|
|
81
|
+
return "muy_corto"
|
|
82
|
+
elif segundos <= 90:
|
|
83
|
+
return "ideal"
|
|
84
|
+
elif segundos <= 179:
|
|
85
|
+
return "largo"
|
|
86
|
+
else:
|
|
87
|
+
return "muy_largo"
|
|
88
|
+
|
|
89
|
+
if DURACION:
|
|
90
|
+
clips = [
|
|
91
|
+
c for c in clips
|
|
92
|
+
if clasifica(hhmmss_to_sec(c["end"]) - hhmmss_to_sec(c["start"])) == DURACION
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
excel_data = []
|
|
96
|
+
for c in clips:
|
|
97
|
+
duracion = round(hhmmss_to_sec(
|
|
98
|
+
c["end"]) - hhmmss_to_sec(c["start"]), 2)
|
|
99
|
+
excel_data.append({
|
|
100
|
+
"slug": c.get("slug"),
|
|
101
|
+
"titulo": c.get("titulo"),
|
|
102
|
+
"descripcion": c.get("descripcion", ""),
|
|
103
|
+
"feeling": c.get("feeling", ""),
|
|
104
|
+
"category": c.get("category", ""),
|
|
105
|
+
"start": c.get("start"),
|
|
106
|
+
"end": c.get("end"),
|
|
107
|
+
"duracion_segundos": duracion,
|
|
108
|
+
"duracion_mmss": f"{int(duracion // 60)}:{int(duracion % 60):02}",
|
|
109
|
+
"clasificacion_duracion": clasifica(duracion),
|
|
110
|
+
"fecha_publicacion": "",
|
|
111
|
+
"estado": "por_publicar"
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
df = pd.DataFrame(excel_data)
|
|
115
|
+
df.to_excel(ROOT_DIR / "estado_clips.xlsx", index=False)
|
|
116
|
+
|
|
117
|
+
if VIDEO_H:
|
|
118
|
+
print("🎬 Procesando clips del video horizontal (sin offset)...")
|
|
119
|
+
for i, c in enumerate(tqdm(clips, desc="Horizontal")):
|
|
120
|
+
start = hhmmss_to_sec(c["start"])
|
|
121
|
+
end = hhmmss_to_sec(c["end"])
|
|
122
|
+
slug = slugify(c.get("slug", f"clip_{i:02d}"))
|
|
123
|
+
out = str(OUT_H / f"{i:02d}_{slug}.mp4").replace("\\", "/")
|
|
124
|
+
cmd = f'ffmpeg -y -hide_banner -loglevel error -ss {sec_to_hhmmss(start)} -to {sec_to_hhmmss(end)} -i "{VIDEO_H}" -c copy "{out}"'
|
|
125
|
+
if DRY_RUN:
|
|
126
|
+
print("[DRY RUN]", cmd)
|
|
127
|
+
else:
|
|
128
|
+
subprocess.run(cmd, shell=True, check=True)
|
|
129
|
+
|
|
130
|
+
if VIDEO_V:
|
|
131
|
+
print("🎬 Procesando clips del video vertical (con offset aplicado)...")
|
|
132
|
+
for i, c in enumerate(tqdm(clips, desc="Vertical")):
|
|
133
|
+
start = hhmmss_to_sec(c["start"]) - OFFSET_SECONDS
|
|
134
|
+
end = hhmmss_to_sec(c["end"]) - OFFSET_SECONDS
|
|
135
|
+
slug = slugify(c.get("slug", f"clip_{i:02d}"))
|
|
136
|
+
out = str(OUT_V / f"{i:02d}_{slug}.mp4").replace("\\", "/")
|
|
137
|
+
cmd = f'ffmpeg -y -hide_banner -loglevel error -ss {sec_to_hhmmss(start)} -to {sec_to_hhmmss(end)} -i "{VIDEO_V}" -c copy "{out}"'
|
|
138
|
+
if DRY_RUN:
|
|
139
|
+
print("[DRY RUN]", cmd)
|
|
140
|
+
else:
|
|
141
|
+
subprocess.run(cmd, shell=True, check=True)
|
|
142
|
+
|
|
143
|
+
print("\n✅ ¡Listo! Clips generados:")
|
|
144
|
+
if VIDEO_H:
|
|
145
|
+
print("📁", OUT_H)
|
|
146
|
+
if VIDEO_V:
|
|
147
|
+
print("📁", OUT_V)
|
|
148
|
+
print("🗂️ Archivo Excel:", ROOT_DIR / "estado_clips.xlsx")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
if __name__ == "__main__":
|
|
152
|
+
main()
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clipkly
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Corta automáticamente clips de video a partir de subtítulos o timecodes definidos en un JSON.
|
|
5
|
+
Home-page: https://github.com/judlup/clipkly
|
|
6
|
+
Author: Julian Dario Luna Patiño
|
|
7
|
+
Author-email: judlup@trycatch.tv
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Requires-Dist: tqdm
|
|
16
|
+
Requires-Dist: openpyxl
|
|
17
|
+
Dynamic: author
|
|
18
|
+
Dynamic: author-email
|
|
19
|
+
Dynamic: classifier
|
|
20
|
+
Dynamic: description
|
|
21
|
+
Dynamic: description-content-type
|
|
22
|
+
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# 🎬 Clipkly
|
|
30
|
+
|
|
31
|
+
`Clipkly` es una herramienta en Python para cortar automáticamente clips desde dos versiones de un mismo video (horizontal y vertical), usando un archivo `.json` con los timecodes de los mejores momentos.
|
|
32
|
+
|
|
33
|
+
Soporta desfase (`--offset`) cuando el video vertical empieza en un punto diferente al horizontal (por ejemplo, por edición o plataformas como TikTok).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Uso rápido
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python clipkly.py --offset 403.025 --vertical video_v.mp4
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Esto generará clips desde:
|
|
44
|
+
|
|
45
|
+
- `video_v.mp4` (con offset aplicado) → en `clips/vertical/`
|
|
46
|
+
|
|
47
|
+
Si también incluyes `--horizontal`, generará clips desde `video_h.mp4` (sin offset) → en `clips/horizontal/`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🧾 Formato del archivo clips.json
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
[
|
|
55
|
+
{
|
|
56
|
+
"start": "01:46:31.760",
|
|
57
|
+
"end": "01:47:17.199",
|
|
58
|
+
"slug": "titulo_del_video",
|
|
59
|
+
"titulo": "titulo del video optimizado para SEO",
|
|
60
|
+
"descripcion": "breve descripción del contenido del clip",
|
|
61
|
+
"feeling": "sentimiento del clip",
|
|
62
|
+
"category" : "categoria del clip"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
🧠 Puedes generar este archivo a partir de los subtítulos de YouTube (ver más abajo).
|
|
68
|
+
|
|
69
|
+
📥 Cómo obtener los subtítulos (`.srt`):
|
|
70
|
+
|
|
71
|
+
1. Ve a https://www.downloadyoutubesubtitles.com/es
|
|
72
|
+
2. Pega el enlace del video de YouTube
|
|
73
|
+
3. Descarga el archivo en formato `.srt` (idealmente en español)
|
|
74
|
+
4. Abre Google AI Studio
|
|
75
|
+
5. Usa este prompt:
|
|
76
|
+
|
|
77
|
+
```plaintext
|
|
78
|
+
Estos son los subtítulos de la transmisión, puede darme la lista de los mejores momentos para sacar los clips, también en formato JSON:
|
|
79
|
+
|
|
80
|
+
{
|
|
81
|
+
"start": "01:46:31.760",
|
|
82
|
+
"end": "01:47:17.199",
|
|
83
|
+
"slug": "titulo_del_video",
|
|
84
|
+
"titulo": "titulo del video optimizado para seo",
|
|
85
|
+
"description": "descripción o sinapsis del video",
|
|
86
|
+
"feeling": "sentimiento del clip",
|
|
87
|
+
"category" : " categoria del clip"
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ⚙️ Argumentos del script
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python clipkly.py [opciones]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Opción | Descripción |
|
|
100
|
+
|------------------|-----------------------------------------------------------------------------|
|
|
101
|
+
| `--offset` | Offset en segundos solo para el video vertical (default: 0.0) |
|
|
102
|
+
| `--horizontal` | Ruta al video horizontal (opcional) |
|
|
103
|
+
| `--vertical` | Ruta al video vertical (opcional) |
|
|
104
|
+
| `--json` | Ruta al archivo JSON con los timecodes (default: `clips.json`) |
|
|
105
|
+
| `--filter` | Filtra los clips por categoría (`inspiracional`, `tips`, etc.) |
|
|
106
|
+
| `--duracion` | Filtra clips por duración: `muy_corto`, `ideal`, `largo`, `muy_largo` |
|
|
107
|
+
| `--dry-run` | Muestra los comandos sin ejecutarlos |
|
|
108
|
+
| `--version`, `-v`| Muestra la versión del script |
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 📁 Estructura generada
|
|
113
|
+
|
|
114
|
+
```plaintext
|
|
115
|
+
📁 clips/
|
|
116
|
+
├── 📁 horizontal/ ← Clips del video horizontal (sin offset)
|
|
117
|
+
├── 📁 vertical/ ← Clips del video vertical (con offset)
|
|
118
|
+
└── 📄 estado_clips.xlsx ← Archivo Excel con metadatos editoriales
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Este Excel incluye:
|
|
122
|
+
|
|
123
|
+
- duración del clip en segundos (`duracion_segundos`)
|
|
124
|
+
- duración en formato `min:seg` (`duracion_mmss`)
|
|
125
|
+
- clasificación automática:
|
|
126
|
+
- `muy_corto`: hasta 30s
|
|
127
|
+
- `ideal`: entre 31s y 90s
|
|
128
|
+
- `largo`: entre 91s y 179s
|
|
129
|
+
- `muy_largo`: más de 3 minutos
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 📦 Requisitos
|
|
134
|
+
|
|
135
|
+
- Python 3.7+
|
|
136
|
+
- `ffmpeg` instalado y en el PATH
|
|
137
|
+
- Ejecutar: `pip install -r requirements.txt` para instalar dependencias
|
|
138
|
+
|
|
139
|
+
Dependencias mínimas:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
pandas
|
|
143
|
+
tqdm
|
|
144
|
+
openpyxl
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 💡 Funcionalidades
|
|
150
|
+
|
|
151
|
+
- ✂️ Corta clips precisos con FFmpeg sin recodificar (`-c copy`)
|
|
152
|
+
- 🕒 Aplica offset únicamente al video vertical
|
|
153
|
+
- 📁 Genera carpetas separadas para cada versión (`clips/horizontal` y `clips/vertical`)
|
|
154
|
+
- 🧼 Limpia automáticamente los nombres de archivo
|
|
155
|
+
- 🧠 Exporta Excel para planeación editorial (`estado_clips.xlsx`)
|
|
156
|
+
- 🔍 Filtros por duración y categoría
|
|
157
|
+
- 🔄 Modo `--dry-run` para validar sin ejecutar
|
|
158
|
+
- 🧾 Modo CLI personalizable
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 🖥️ Alias útil en consola
|
|
163
|
+
|
|
164
|
+
Powershell:
|
|
165
|
+
|
|
166
|
+
```powershell
|
|
167
|
+
Set-Alias clipkly python ./clipkly.py
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Linux/Bash:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
alias clipkly='python ./clipkly.py'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Entonces puedes correr:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
clipkly --offset 3.25 --vertical video_v.mp4
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 📌 Ejemplo completo
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python clipkly.py --offset 403.025 --horizontal video_h.mp4 --vertical video_v.mp4 --json clips.json --duracion ideal
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🔜 Próximas mejoras sugeridas
|
|
193
|
+
|
|
194
|
+
- 🎞️ Subtítulos quemados en los clips
|
|
195
|
+
- 📸 Generación de miniaturas automáticas
|
|
196
|
+
- 🌐 Interfaz gráfica simple con Gradio
|
|
197
|
+
- 📊 Dashboard para gestión de clips y publicaciones
|
|
198
|
+
- ☁️ Integración con plataformas como Notion o Google Sheets
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 🙌 Créditos
|
|
203
|
+
|
|
204
|
+
Este proyecto fue creado por **Julian Dario Luna Patiño**, ingeniero de software, arquitecto de soluciones en la nube y creador de contenido en [TryCatch.tv](https://trycatch.tv).
|
|
205
|
+
|
|
206
|
+
**clipkly** nació como una herramienta práctica para automatizar la creación de clips a partir de transmisiones en vivo, especialmente útil para quienes trabajan con contenido en plataformas como YouTube, TikTok e Instagram.
|
|
207
|
+
|
|
208
|
+
Contacto: [judlup@trycatch.tv](mailto:judlup@trycatch.tv)
|
|
209
|
+
|
|
210
|
+
✨ Dedicado con cariño a **Nikol Daniela** ❤️
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
clipkly/__init__.py
|
|
7
|
+
clipkly/cli.py
|
|
8
|
+
clipkly.egg-info/PKG-INFO
|
|
9
|
+
clipkly.egg-info/SOURCES.txt
|
|
10
|
+
clipkly.egg-info/dependency_links.txt
|
|
11
|
+
clipkly.egg-info/entry_points.txt
|
|
12
|
+
clipkly.egg-info/requires.txt
|
|
13
|
+
clipkly.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
clipkly
|
clipkly-0.1.0/setup.cfg
ADDED
clipkly-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import find_packages, setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="clipkly",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=["pandas", "tqdm", "openpyxl"],
|
|
8
|
+
entry_points={
|
|
9
|
+
"console_scripts": [
|
|
10
|
+
"clipkly=clipkly.cli:main"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
author="Julian Dario Luna Patiño",
|
|
14
|
+
author_email="judlup@trycatch.tv",
|
|
15
|
+
description="Corta automáticamente clips de video a partir de subtítulos o timecodes definidos en un JSON.",
|
|
16
|
+
long_description=open("README.md", encoding="utf-8").read(),
|
|
17
|
+
long_description_content_type="text/markdown",
|
|
18
|
+
url="https://github.com/judlup/clipkly",
|
|
19
|
+
classifiers=[
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
],
|
|
24
|
+
python_requires=">=3.7",
|
|
25
|
+
)
|