malvin noel commited on
Commit
aef0378
Β·
1 Parent(s): 6b9a6b5

change script

Browse files
Files changed (1) hide show
  1. scripts/generate_scripts.py +86 -37
scripts/generate_scripts.py CHANGED
@@ -1,89 +1,138 @@
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
- import json
4
- import torch
5
- from transformers import AutoModelForCausalLM, AutoTokenizer
6
- import gradio as gr
7
- from dotenv import load_dotenv
8
  import spaces
 
9
  from transformers import AutoModelForCausalLM, AutoTokenizer
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
 
 
 
12
 
 
 
 
 
 
 
 
 
 
13
 
14
- @spaces.GPU()
15
- def generate_local(prompt: str, max_new_tokens: int = 350, temperature: float = 0.7) -> str:
16
- model_id = "Qwen/Qwen3-0.6B"
17
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # get the device the model is on
18
 
19
- tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
20
- model = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype=torch.float32, trust_remote_code=True).to(device)
21
- inputs = tokenizer(prompt, return_tensors="pt").to(device)
22
-
23
  output_ids = model.generate(
24
  **inputs,
25
  max_new_tokens=max_new_tokens,
26
  do_sample=True,
27
- temperature=temperature,
28
  pad_token_id=tokenizer.eos_token_id,
29
  )
30
- return tokenizer.decode(output_ids[0], skip_special_tokens=True)
 
 
 
 
31
 
 
32
 
33
 
34
- def generate_script(prompt: str, word_count: int = 60) -> str:
 
 
35
  system_prompt = (
36
  "You are an expert YouTube scriptwriter. "
37
  "Your job is to write the EXACT words that will be spoken aloud in a video. "
38
- f"Topic: {prompt.strip()}\n\n"
39
  "🎯 Output rules:\n"
40
  f"- Exactly {word_count} words.\n"
41
  "- Only the spoken words. NO scene descriptions, instructions, or formatting.\n"
42
- "- Write in natural, clear, and simple English, as if it's being said by a voiceover artist.\n"
43
  "- Keep a steady rhythm (about 2 words per second).\n"
44
  "- Do NOT include any explanations, labels, or headers. Only output the final spoken script.\n\n"
45
  "Start now:"
46
  )
47
- return generate_local(system_prompt)
 
48
 
 
 
49
 
50
- def one_word(query: str) -> str:
51
- prompt_final = (
52
- "Extract only the unique central theme of the following text in English in JSON format like this: "
53
- '{"keyword": "impact"}. Text: ' + query
54
  )
55
- result = generate_local(prompt_final, max_new_tokens=30, temperature=0.4)
 
 
56
  try:
57
- keyword_json = json.loads(result)
58
- keyword = keyword_json.get("keyword", "")
59
  except json.JSONDecodeError:
60
- matches = re.findall(r'\b[a-zA-Z]{3,}\b', result)
 
61
  keyword = matches[0] if matches else ""
62
  return keyword.lower()
63
 
64
 
65
  def generate_title(text: str) -> str:
66
- prompt_final = (
67
  "Generate a unique title for a YouTube Short video that is engaging and informative, "
68
- "maximum 100 characters, without emojis, introduction, or explanation. Content:\n" + text
69
  )
70
- return generate_local(prompt_final, max_new_tokens=50, temperature=0.9).strip()
 
71
 
72
  def generate_description(text: str) -> str:
73
- prompt_final = (
74
  "Write only the YouTube video description in English:\n"
75
  "1. A compelling opening line.\n"
76
  "2. A clear summary of the video (max 3 lines).\n"
77
  "3. End with 3 relevant hashtags.\n"
78
  "No emojis or introductions. Here is the text:\n" + text
79
  )
80
- return generate_local(prompt_final, max_new_tokens=300, temperature=0.7).strip()
81
 
82
- def generate_tags(text: str) -> list:
83
- prompt_final = (
 
84
  "List only the important keywords for this YouTube video, separated by commas, "
85
  "maximum 10 keywords. Context: " + text
86
  )
87
- result = generate_local(prompt_final, max_new_tokens=100, temperature=0.5)
88
- return [tag.strip() for tag in result.split(",") if tag.strip()]
89
-
 
1
+ """Reusable helpers for YouTube‑content generation.
2
+ Optimisations applied:
3
+ β€’ Model + tokenizer are loaded **once** at import‑time, not per call.
4
+ β€’ FP16 + `device_map=\"auto\"` for smaller VRAM + faster inference.
5
+ β€’ `@spaces.GPU()` decorator keeps the worker on a GPU Space.
6
+ β€’ All generation helpers reuse a single `generate_local()` for consistency.
7
+ β€’ Minimal error handling + regex fallback when JSON parsing fails.
8
+ """
9
+
10
+ import json
11
  import os
12
  import re
13
+ from typing import List
14
+
 
 
 
15
  import spaces
16
+ import torch
17
  from transformers import AutoModelForCausalLM, AutoTokenizer
18
 
19
+ # ─────────────────────────────────────────────────────────────
20
+ # Model initialisation (runs ONCE per Space replica)
21
+ # ─────────────────────────────────────────────────────────────
22
+ MODEL_ID = os.getenv("LLM_ID", "Qwen/Qwen3-0.6B")
23
+ DTYPE = torch.float16 # fp16 fits comfortably on free‑tier A10G/ T4
24
+
25
+ # Load tokenizer + model once; they live for the lifetime of the process
26
+ print(f"πŸ”„ Loading model {MODEL_ID} …")
27
+
28
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
29
+ model = (
30
+ AutoModelForCausalLM
31
+ .from_pretrained(
32
+ MODEL_ID,
33
+ torch_dtype=DTYPE,
34
+ device_map="auto", # puts weights straight on the first CUDA device if available
35
+ trust_remote_code=True,
36
+ )
37
+ .eval()
38
+ ) # .eval() disables dropout β†’ deterministic + minor speed boost
39
+ print("βœ… Model loaded once.")
40
+
41
+ # Prevent accidental CPU fallback when GPU memory is full
42
+ DEVICE = model.device
43
 
44
+ # ─────────────────────────────────────────────────────────────
45
+ # Core text‑generation helper
46
+ # ─────────────────────────────────────────────────────────────
47
 
48
+ @spaces.GPU() # Ensures this worker stays on a GPU node in HF Spaces
49
+ @torch.inference_mode() # no_grad + autocast under the hood in 2.2+
50
+ def generate_local(
51
+ prompt: str,
52
+ *,
53
+ max_new_tokens: int = 350,
54
+ temperature: float = 0.7,
55
+ ) -> str:
56
+ """Low‑level wrapper around `model.generate()` using the shared model."""
57
 
58
+ inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
 
 
 
59
 
 
 
 
 
60
  output_ids = model.generate(
61
  **inputs,
62
  max_new_tokens=max_new_tokens,
63
  do_sample=True,
64
+ temperature=float(temperature), # ensure JSON‑serialisable types are cast properly
65
  pad_token_id=tokenizer.eos_token_id,
66
  )
67
+ return tokenizer.decode(output_ids[0], skip_special_tokens=True).strip()
68
+
69
+ # ─────────────────────────────────────────────────────────────
70
+ # High‑level helpers for YouTube workflow
71
+ # ─────────────────────────────────────────────────────────────
72
 
73
+ WORDS_PER_SECOND = 2 # used by callers to estimate length; not critical here
74
 
75
 
76
+ def generate_script(topic: str, word_count: int = 60) -> str:
77
+ """Return a *spoken* script of exactly `word_count` words on `topic`."""
78
+
79
  system_prompt = (
80
  "You are an expert YouTube scriptwriter. "
81
  "Your job is to write the EXACT words that will be spoken aloud in a video. "
82
+ f"Topic: {topic.strip()}\n\n"
83
  "🎯 Output rules:\n"
84
  f"- Exactly {word_count} words.\n"
85
  "- Only the spoken words. NO scene descriptions, instructions, or formatting.\n"
86
+ "- Write in natural, clear, and simple English, as if it's being said by a voice‑over artist.\n"
87
  "- Keep a steady rhythm (about 2 words per second).\n"
88
  "- Do NOT include any explanations, labels, or headers. Only output the final spoken script.\n\n"
89
  "Start now:"
90
  )
91
+ return generate_local(system_prompt, max_new_tokens=word_count * 2, temperature=0.8)
92
+
93
 
94
+ def one_word(text: str) -> str:
95
+ """Extract a single keyword that summarises *text*. Returns lowercase string."""
96
 
97
+ prompt = (
98
+ "Extract only the unique central theme of the following text in English "
99
+ "as JSON: {\"keyword\": \"impact\"}. Text: " + text
 
100
  )
101
+ result = generate_local(prompt, max_new_tokens=30, temperature=0.4)
102
+
103
+ # Try JSON first
104
  try:
105
+ keyword = json.loads(result).get("keyword", "")
 
106
  except json.JSONDecodeError:
107
+ # Fallback: pick first 3+ letter word
108
+ matches = re.findall(r"\\b[a-zA-Z]{3,}\\b", result)
109
  keyword = matches[0] if matches else ""
110
  return keyword.lower()
111
 
112
 
113
  def generate_title(text: str) -> str:
114
+ prompt = (
115
  "Generate a unique title for a YouTube Short video that is engaging and informative, "
116
+ "max 100 characters, without emojis, introduction, or explanation. Content:\n" + text
117
  )
118
+ return generate_local(prompt, max_new_tokens=50, temperature=0.9)
119
+
120
 
121
  def generate_description(text: str) -> str:
122
+ prompt = (
123
  "Write only the YouTube video description in English:\n"
124
  "1. A compelling opening line.\n"
125
  "2. A clear summary of the video (max 3 lines).\n"
126
  "3. End with 3 relevant hashtags.\n"
127
  "No emojis or introductions. Here is the text:\n" + text
128
  )
129
+ return generate_local(prompt, max_new_tokens=300, temperature=0.7)
130
 
131
+
132
+ def generate_tags(text: str) -> List[str]:
133
+ prompt = (
134
  "List only the important keywords for this YouTube video, separated by commas, "
135
  "maximum 10 keywords. Context: " + text
136
  )
137
+ raw = generate_local(prompt, max_new_tokens=100, temperature=0.5)
138
+ return [tag.strip() for tag in raw.split(",") if tag.strip()]